Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ a1f445d3

History | View | Annotate | Download (64 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 AddChild(self, device):
705
    """Add a new member 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
    if device.dev_path is None:
711
      raise errors.BlockDeviceError("New child is not initialised")
712
    result = utils.RunCmd(["mdadm", "-a", self.dev_path, device.dev_path])
713
    if result.failed:
714
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
715
                                    result.output)
716
    new_len = len(self._children) + 1
717
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
718
    if result.failed:
719
      raise errors.BlockDeviceError("Can't grow md array: %s" %
720
                                    result.output)
721
    self._children.append(device)
722

    
723

    
724
  def RemoveChild(self, dev_path):
725
    """Remove member from the md raid1.
726

727
    """
728
    if self.minor is None and not self.Attach():
729
      raise errors.BlockDeviceError("Can't attach to device")
730
    if len(self._children) == 1:
731
      raise errors.BlockDeviceError("Can't reduce member when only one"
732
                                    " child left")
733
    for device in self._children:
734
      if device.dev_path == dev_path:
735
        break
736
    else:
737
      raise errors.BlockDeviceError("Can't find child with this path")
738
    new_len = len(self._children) - 1
739
    result = utils.RunCmd(["mdadm", "-f", self.dev_path, dev_path])
740
    if result.failed:
741
      raise errors.BlockDeviceError("Failed to mark device as failed: %s" %
742
                                    result.output)
743

    
744
    # it seems here we need a short delay for MD to update its
745
    # superblocks
746
    time.sleep(0.5)
747
    result = utils.RunCmd(["mdadm", "-r", self.dev_path, dev_path])
748
    if result.failed:
749
      raise errors.BlockDeviceError("Failed to remove device from array:"
750
                                        " %s" % result.output)
751
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
752
                           "-n", new_len])
753
    if result.failed:
754
      raise errors.BlockDeviceError("Can't shrink md array: %s" %
755
                                    result.output)
756
    self._children.remove(device)
757

    
758

    
759
  def GetStatus(self):
760
    """Return the status of the device.
761

762
    """
763
    self.Attach()
764
    if self.minor is None:
765
      retval = self.STATUS_UNKNOWN
766
    else:
767
      retval = self.STATUS_ONLINE
768
    return retval
769

    
770

    
771
  def _SetFromMinor(self, minor):
772
    """Set our parameters based on the given minor.
773

774
    This sets our minor variable and our dev_path.
775

776
    """
777
    self.minor = minor
778
    self.dev_path = "/dev/md%d" % minor
779

    
780

    
781
  def Assemble(self):
782
    """Assemble the MD device.
783

784
    At this point we should have:
785
      - list of children devices
786
      - uuid
787

788
    """
789
    result = super(MDRaid1, self).Assemble()
790
    if not result:
791
      return result
792
    md_list = self._GetUsedDevs()
793
    for minor in md_list:
794
      info = self._GetDevInfo(minor)
795
      if info and info["uuid"] == self.unique_id:
796
        self._SetFromMinor(minor)
797
        logger.Info("MD array %s already started" % str(self))
798
        return True
799
    free_minor = self._FindUnusedMinor()
800
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
801
                           self.unique_id, "/dev/md%d" % free_minor] +
802
                          [bdev.dev_path for bdev in self._children])
803
    if result.failed:
804
      logger.Error("Can't assemble MD array: %s: %s" %
805
                   (result.fail_reason, result.output))
806
      self.minor = None
807
    else:
808
      self.minor = free_minor
809
    return not result.failed
810

    
811

    
812
  def Shutdown(self):
813
    """Tear down the MD array.
814

815
    This does a 'mdadm --stop' so after this command, the array is no
816
    longer available.
817

818
    """
819
    if self.minor is None and not self.Attach():
820
      logger.Info("MD object not attached to a device")
821
      return True
822

    
823
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
824
    if result.failed:
825
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
826
      return False
827
    self.minor = None
828
    self.dev_path = None
829
    return True
830

    
831

    
832
  def SetSyncSpeed(self, kbytes):
833
    """Set the maximum sync speed for the MD array.
834

835
    """
836
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
837
    if self.minor is None:
838
      logger.Error("MD array not attached to a device")
839
      return False
840
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
841
    try:
842
      f.write("%d" % kbytes)
843
    finally:
844
      f.close()
845
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
846
    try:
847
      f.write("%d" % (kbytes/2))
848
    finally:
849
      f.close()
850
    return result
851

    
852

    
853
  def GetSyncStatus(self):
854
    """Returns the sync status of the device.
855

856
    Returns:
857
     (sync_percent, estimated_time)
858

859
    If sync_percent is None, it means all is ok
860
    If estimated_time is None, it means we can't esimate
861
    the time needed, otherwise it's the time left in seconds
862

863
    """
864
    if self.minor is None and not self.Attach():
865
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
866
    dev_info = self._GetDevInfo(self.minor)
867
    is_clean = ("state" in dev_info and
868
                len(dev_info["state"]) == 1 and
869
                dev_info["state"][0] in ("clean", "active"))
870
    sys_path = "/sys/block/md%s/md/" % self.minor
871
    f = file(sys_path + "sync_action")
872
    sync_status = f.readline().strip()
873
    f.close()
874
    if sync_status == "idle":
875
      return None, None, not is_clean
876
    f = file(sys_path + "sync_completed")
877
    sync_completed = f.readline().strip().split(" / ")
878
    f.close()
879
    if len(sync_completed) != 2:
880
      return 0, None, not is_clean
881
    sync_done, sync_total = [float(i) for i in sync_completed]
882
    sync_percent = 100.0*sync_done/sync_total
883
    f = file(sys_path + "sync_speed")
884
    sync_speed_k = int(f.readline().strip())
885
    if sync_speed_k == 0:
886
      time_est = None
887
    else:
888
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
889
    return sync_percent, time_est, not is_clean
890

    
891

    
892
  def Open(self, force=False):
893
    """Make the device ready for I/O.
894

895
    This is a no-op for the MDRaid1 device type, although we could use
896
    the 2.6.18's new array_state thing.
897

898
    """
899
    return True
900

    
901

    
902
  def Close(self):
903
    """Notifies that the device will no longer be used for I/O.
904

905
    This is a no-op for the MDRaid1 device type, but see comment for
906
    `Open()`.
907

908
    """
909
    return True
910

    
911

    
912
class BaseDRBD(BlockDev):
913
  """Base DRBD class.
914

915
  This class contains a few bits of common functionality between the
916
  0.7 and 8.x versions of DRBD.
917

918
  """
919
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
920
                           r" \(api:(\d+)/proto:(\d+)\)")
921
  _DRBD_MAJOR = 147
922
  _ST_UNCONFIGURED = "Unconfigured"
923
  _ST_WFCONNECTION = "WFConnection"
924
  _ST_CONNECTED = "Connected"
925

    
926
  @staticmethod
927
  def _GetProcData():
928
    """Return data from /proc/drbd.
929

930
    """
931
    stat = open("/proc/drbd", "r")
932
    try:
933
      data = stat.read().splitlines()
934
    finally:
935
      stat.close()
936
    if not data:
937
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
938
    return data
939

    
940
  @staticmethod
941
  def _MassageProcData(data):
942
    """Transform the output of _GetProdData into a nicer form.
943

944
    Returns:
945
      a dictionary of minor: joined lines from /proc/drbd for that minor
946

947
    """
948
    lmatch = re.compile("^ *([0-9]+):.*$")
949
    results = {}
950
    old_minor = old_line = None
951
    for line in data:
952
      lresult = lmatch.match(line)
953
      if lresult is not None:
954
        if old_minor is not None:
955
          results[old_minor] = old_line
956
        old_minor = int(lresult.group(1))
957
        old_line = line
958
      else:
959
        if old_minor is not None:
960
          old_line += " " + line.strip()
961
    # add last line
962
    if old_minor is not None:
963
      results[old_minor] = old_line
964
    return results
965

    
966
  @classmethod
967
  def _GetVersion(cls):
968
    """Return the DRBD version.
969

970
    This will return a list [k_major, k_minor, k_point, api, proto].
971

972
    """
973
    proc_data = cls._GetProcData()
974
    first_line = proc_data[0].strip()
975
    version = cls._VERSION_RE.match(first_line)
976
    if not version:
977
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
978
                                    first_line)
979
    return [int(val) for val in version.groups()]
980

    
981
  @staticmethod
982
  def _DevPath(minor):
983
    """Return the path to a drbd device for a given minor.
984

985
    """
986
    return "/dev/drbd%d" % minor
987

    
988
  @classmethod
989
  def _GetUsedDevs(cls):
990
    """Compute the list of used DRBD devices.
991

992
    """
993
    data = cls._GetProcData()
994

    
995
    used_devs = {}
996
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
997
    for line in data:
998
      match = valid_line.match(line)
999
      if not match:
1000
        continue
1001
      minor = int(match.group(1))
1002
      state = match.group(2)
1003
      if state == cls._ST_UNCONFIGURED:
1004
        continue
1005
      used_devs[minor] = state, line
1006

    
1007
    return used_devs
1008

    
1009
  def _SetFromMinor(self, minor):
1010
    """Set our parameters based on the given minor.
1011

1012
    This sets our minor variable and our dev_path.
1013

1014
    """
1015
    if minor is None:
1016
      self.minor = self.dev_path = None
1017
    else:
1018
      self.minor = minor
1019
      self.dev_path = self._DevPath(minor)
1020

    
1021
  @staticmethod
1022
  def _CheckMetaSize(meta_device):
1023
    """Check if the given meta device looks like a valid one.
1024

1025
    This currently only check the size, which must be around
1026
    128MiB.
1027

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

    
1047

    
1048
class DRBDev(BaseDRBD):
1049
  """DRBD block device.
1050

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

1055
  The unique_id for the drbd device is the (local_ip, local_port,
1056
  remote_ip, remote_port) tuple, and it must have two children: the
1057
  data device and the meta_device. The meta device is checked for
1058
  valid size and is zeroed on create.
1059

1060
  """
1061
  def __init__(self, unique_id, children):
1062
    super(DRBDev, self).__init__(unique_id, children)
1063
    self.major = self._DRBD_MAJOR
1064
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1065
    if kmaj != 0 and kmin != 7:
1066
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1067
                                    " requested ganeti usage: kernel is"
1068
                                    " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1069

    
1070
    if len(children) != 2:
1071
      raise ValueError("Invalid configuration data %s" % str(children))
1072
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1073
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1074
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1075
    self.Attach()
1076

    
1077
  @classmethod
1078
  def _FindUnusedMinor(cls):
1079
    """Find an unused DRBD device.
1080

1081
    """
1082
    data = cls._GetProcData()
1083

    
1084
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1085
    for line in data:
1086
      match = valid_line.match(line)
1087
      if match:
1088
        return int(match.group(1))
1089
    logger.Error("Error: no free drbd minors!")
1090
    raise errors.BlockDeviceError("Can't find a free DRBD minor")
1091

    
1092
  @classmethod
1093
  def _GetDevInfo(cls, minor):
1094
    """Get details about a given DRBD minor.
1095

1096
    This return, if available, the local backing device in (major,
1097
    minor) formant and the local and remote (ip, port) information.
1098

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

    
1143

    
1144
  def _MatchesLocal(self, info):
1145
    """Test if our local config matches with an existing device.
1146

1147
    The parameter should be as returned from `_GetDevInfo()`. This
1148
    method tests if our local backing device is the same as the one in
1149
    the info parameter, in effect testing if we look like the given
1150
    device.
1151

1152
    """
1153
    if not ("local_dev" in info and "meta_dev" in info and
1154
            "meta_index" in info):
1155
      return False
1156

    
1157
    backend = self._children[0]
1158
    if backend is not None:
1159
      retval = (info["local_dev"] == (backend.major, backend.minor))
1160
    else:
1161
      retval = (info["local_dev"] == (0, 0))
1162
    meta = self._children[1]
1163
    if meta is not None:
1164
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1165
      retval = retval and (info["meta_index"] == 0)
1166
    else:
1167
      retval = retval and (info["meta_dev"] == "internal" and
1168
                           info["meta_index"] == -1)
1169
    return retval
1170

    
1171

    
1172
  def _MatchesNet(self, info):
1173
    """Test if our network config matches with an existing device.
1174

1175
    The parameter should be as returned from `_GetDevInfo()`. This
1176
    method tests if our network configuration is the same as the one
1177
    in the info parameter, in effect testing if we look like the given
1178
    device.
1179

1180
    """
1181
    if (((self._lhost is None and not ("local_addr" in info)) and
1182
         (self._rhost is None and not ("remote_addr" in info)))):
1183
      return True
1184

    
1185
    if self._lhost is None:
1186
      return False
1187

    
1188
    if not ("local_addr" in info and
1189
            "remote_addr" in info):
1190
      return False
1191

    
1192
    retval = (info["local_addr"] == (self._lhost, self._lport))
1193
    retval = (retval and
1194
              info["remote_addr"] == (self._rhost, self._rport))
1195
    return retval
1196

    
1197

    
1198
  @classmethod
1199
  def _AssembleLocal(cls, minor, backend, meta):
1200
    """Configure the local part of a DRBD device.
1201

1202
    This is the first thing that must be done on an unconfigured DRBD
1203
    device. And it must be done only once.
1204

1205
    """
1206
    if not cls._CheckMetaSize(meta):
1207
      return False
1208
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1209
                           backend, meta, "0", "-e", "detach"])
1210
    if result.failed:
1211
      logger.Error("Can't attach local disk: %s" % result.output)
1212
    return not result.failed
1213

    
1214

    
1215
  @classmethod
1216
  def _ShutdownLocal(cls, minor):
1217
    """Detach from the local device.
1218

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

1222
    """
1223
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1224
    if result.failed:
1225
      logger.Error("Can't detach local device: %s" % result.output)
1226
    return not result.failed
1227

    
1228

    
1229
  @staticmethod
1230
  def _ShutdownAll(minor):
1231
    """Deactivate the device.
1232

1233
    This will, of course, fail if the device is in use.
1234

1235
    """
1236
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1237
    if result.failed:
1238
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1239
    return not result.failed
1240

    
1241

    
1242
  @classmethod
1243
  def _AssembleNet(cls, minor, net_info, protocol):
1244
    """Configure the network part of the device.
1245

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

1253
    """
1254
    lhost, lport, rhost, rport = net_info
1255
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1256
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1257
                           protocol])
1258
    if result.failed:
1259
      logger.Error("Can't setup network for dbrd device: %s" %
1260
                   result.fail_reason)
1261
      return False
1262

    
1263
    timeout = time.time() + 10
1264
    ok = False
1265
    while time.time() < timeout:
1266
      info = cls._GetDevInfo(minor)
1267
      if not "local_addr" in info or not "remote_addr" in info:
1268
        time.sleep(1)
1269
        continue
1270
      if (info["local_addr"] != (lhost, lport) or
1271
          info["remote_addr"] != (rhost, rport)):
1272
        time.sleep(1)
1273
        continue
1274
      ok = True
1275
      break
1276
    if not ok:
1277
      logger.Error("Timeout while configuring network")
1278
      return False
1279
    return True
1280

    
1281

    
1282
  @classmethod
1283
  def _ShutdownNet(cls, minor):
1284
    """Disconnect from the remote peer.
1285

1286
    This fails if we don't have a local device.
1287

1288
    """
1289
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1290
    logger.Error("Can't shutdown network: %s" % result.output)
1291
    return not result.failed
1292

    
1293

    
1294
  def Assemble(self):
1295
    """Assemble the drbd.
1296

1297
    Method:
1298
      - if we have a local backing device, we bind to it by:
1299
        - checking the list of used drbd devices
1300
        - check if the local minor use of any of them is our own device
1301
        - if yes, abort?
1302
        - if not, bind
1303
      - if we have a local/remote net info:
1304
        - redo the local backing device step for the remote device
1305
        - check if any drbd device is using the local port,
1306
          if yes abort
1307
        - check if any remote drbd device is using the remote
1308
          port, if yes abort (for now)
1309
        - bind our net port
1310
        - bind the remote net port
1311

1312
    """
1313
    self.Attach()
1314
    if self.minor is not None:
1315
      logger.Info("Already assembled")
1316
      return True
1317

    
1318
    result = super(DRBDev, self).Assemble()
1319
    if not result:
1320
      return result
1321

    
1322
    minor = self._FindUnusedMinor()
1323
    need_localdev_teardown = False
1324
    if self._children[0]:
1325
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1326
                                   self._children[1].dev_path)
1327
      if not result:
1328
        return False
1329
      need_localdev_teardown = True
1330
    if self._lhost and self._lport and self._rhost and self._rport:
1331
      result = self._AssembleNet(minor,
1332
                                 (self._lhost, self._lport,
1333
                                  self._rhost, self._rport),
1334
                                 "C")
1335
      if not result:
1336
        if need_localdev_teardown:
1337
          # we will ignore failures from this
1338
          logger.Error("net setup failed, tearing down local device")
1339
          self._ShutdownAll(minor)
1340
        return False
1341
    self._SetFromMinor(minor)
1342
    return True
1343

    
1344

    
1345
  def Shutdown(self):
1346
    """Shutdown the DRBD device.
1347

1348
    """
1349
    if self.minor is None and not self.Attach():
1350
      logger.Info("DRBD device not attached to a device during Shutdown")
1351
      return True
1352
    if not self._ShutdownAll(self.minor):
1353
      return False
1354
    self.minor = None
1355
    self.dev_path = None
1356
    return True
1357

    
1358

    
1359
  def Attach(self):
1360
    """Find a DRBD device which matches our config and attach to it.
1361

1362
    In case of partially attached (local device matches but no network
1363
    setup), we perform the network attach. If successful, we re-test
1364
    the attach if can return success.
1365

1366
    """
1367
    for minor in self._GetUsedDevs():
1368
      info = self._GetDevInfo(minor)
1369
      match_l = self._MatchesLocal(info)
1370
      match_r = self._MatchesNet(info)
1371
      if match_l and match_r:
1372
        break
1373
      if match_l and not match_r and "local_addr" not in info:
1374
        res_r = self._AssembleNet(minor,
1375
                                  (self._lhost, self._lport,
1376
                                   self._rhost, self._rport),
1377
                                  "C")
1378
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1379
          break
1380
    else:
1381
      minor = None
1382

    
1383
    self._SetFromMinor(minor)
1384
    return minor is not None
1385

    
1386

    
1387
  def Open(self, force=False):
1388
    """Make the local state primary.
1389

1390
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1391
    is given. Since this is a pottentialy dangerous operation, the
1392
    force flag should be only given after creation, when it actually
1393
    has to be given.
1394

1395
    """
1396
    if self.minor is None and not self.Attach():
1397
      logger.Error("DRBD cannot attach to a device during open")
1398
      return False
1399
    cmd = ["drbdsetup", self.dev_path, "primary"]
1400
    if force:
1401
      cmd.append("--do-what-I-say")
1402
    result = utils.RunCmd(cmd)
1403
    if result.failed:
1404
      logger.Error("Can't make drbd device primary: %s" % result.output)
1405
      return False
1406
    return True
1407

    
1408

    
1409
  def Close(self):
1410
    """Make the local state secondary.
1411

1412
    This will, of course, fail if the device is in use.
1413

1414
    """
1415
    if self.minor is None and not self.Attach():
1416
      logger.Info("Instance not attached to a device")
1417
      raise errors.BlockDeviceError("Can't find device")
1418
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1419
    if result.failed:
1420
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1421
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1422

    
1423

    
1424
  def SetSyncSpeed(self, kbytes):
1425
    """Set the speed of the DRBD syncer.
1426

1427
    """
1428
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1429
    if self.minor is None:
1430
      logger.Info("Instance not attached to a device")
1431
      return False
1432
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1433
                           kbytes])
1434
    if result.failed:
1435
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1436
    return not result.failed and children_result
1437

    
1438

    
1439
  def GetSyncStatus(self):
1440
    """Returns the sync status of the device.
1441

1442
    Returns:
1443
     (sync_percent, estimated_time)
1444

1445
    If sync_percent is None, it means all is ok
1446
    If estimated_time is None, it means we can't esimate
1447
    the time needed, otherwise it's the time left in seconds
1448

1449
    """
1450
    if self.minor is None and not self.Attach():
1451
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1452
    proc_info = self._MassageProcData(self._GetProcData())
1453
    if self.minor not in proc_info:
1454
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1455
                                    self.minor)
1456
    line = proc_info[self.minor]
1457
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1458
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1459
    if match:
1460
      sync_percent = float(match.group(1))
1461
      hours = int(match.group(2))
1462
      minutes = int(match.group(3))
1463
      seconds = int(match.group(4))
1464
      est_time = hours * 3600 + minutes * 60 + seconds
1465
    else:
1466
      sync_percent = None
1467
      est_time = None
1468
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1469
    if not match:
1470
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1471
                                    self.minor)
1472
    client_state = match.group(1)
1473
    is_degraded = client_state != "Connected"
1474
    return sync_percent, est_time, is_degraded
1475

    
1476

    
1477

    
1478

    
1479
  def GetStatus(self):
1480
    """Compute the status of the DRBD device
1481

1482
    Note that DRBD devices don't have the STATUS_EXISTING state.
1483

1484
    """
1485
    if self.minor is None and not self.Attach():
1486
      return self.STATUS_UNKNOWN
1487

    
1488
    data = self._GetProcData()
1489
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1490
                       self.minor)
1491
    for line in data:
1492
      mresult = match.match(line)
1493
      if mresult:
1494
        break
1495
    else:
1496
      logger.Error("Can't find myself!")
1497
      return self.STATUS_UNKNOWN
1498

    
1499
    state = mresult.group(2)
1500
    if state == "Primary":
1501
      result = self.STATUS_ONLINE
1502
    else:
1503
      result = self.STATUS_STANDBY
1504

    
1505
    return result
1506

    
1507

    
1508
  @staticmethod
1509
  def _ZeroDevice(device):
1510
    """Zero a device.
1511

1512
    This writes until we get ENOSPC.
1513

1514
    """
1515
    f = open(device, "w")
1516
    buf = "\0" * 1048576
1517
    try:
1518
      while True:
1519
        f.write(buf)
1520
    except IOError, err:
1521
      if err.errno != errno.ENOSPC:
1522
        raise
1523

    
1524

    
1525
  @classmethod
1526
  def Create(cls, unique_id, children, size):
1527
    """Create a new DRBD device.
1528

1529
    Since DRBD devices are not created per se, just assembled, this
1530
    function just zeroes the meta device.
1531

1532
    """
1533
    if len(children) != 2:
1534
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1535
    meta = children[1]
1536
    meta.Assemble()
1537
    if not meta.Attach():
1538
      raise errors.BlockDeviceError("Can't attach to meta device")
1539
    if not cls._CheckMetaSize(meta.dev_path):
1540
      raise errors.BlockDeviceError("Invalid meta device")
1541
    logger.Info("Started zeroing device %s" % meta.dev_path)
1542
    cls._ZeroDevice(meta.dev_path)
1543
    logger.Info("Done zeroing device %s" % meta.dev_path)
1544
    return cls(unique_id, children)
1545

    
1546

    
1547
  def Remove(self):
1548
    """Stub remove for DRBD devices.
1549

1550
    """
1551
    return self.Shutdown()
1552

    
1553
class DRBD8(BaseDRBD):
1554
  """DRBD v8.x block device.
1555

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

1560
  The unique_id for the drbd device is the (local_ip, local_port,
1561
  remote_ip, remote_port) tuple, and it must have two children: the
1562
  data device and the meta_device. The meta device is checked for
1563
  valid size and is zeroed on create.
1564

1565
  """
1566
  _DRBD_MAJOR = 147
1567
  _MAX_MINORS = 255
1568
  _PARSE_SHOW = None
1569

    
1570
  def __init__(self, unique_id, children):
1571
    super(DRBD8, self).__init__(unique_id, children)
1572
    self.major = self._DRBD_MAJOR
1573
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1574
    if kmaj != 8:
1575
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1576
                                    " requested ganeti usage: kernel is"
1577
                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1578

    
1579
    if len(children) != 2:
1580
      raise ValueError("Invalid configuration data %s" % str(children))
1581
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1582
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1583
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1584
    self.Attach()
1585

    
1586
  @classmethod
1587
  def _InitMeta(cls, minor, dev_path):
1588
    """Initialize a meta device.
1589

1590
    This will not work if the given minor is in use.
1591

1592
    """
1593
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1594
                           "v08", dev_path, "0", "create-md"])
1595
    if result.failed:
1596
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1597
                                    result.output)
1598

    
1599
  @classmethod
1600
  def _FindUnusedMinor(cls):
1601
    """Find an unused DRBD device.
1602

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

1606
    """
1607
    data = cls._GetProcData()
1608

    
1609
    unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1610
    used_line = re.compile("^ *([0-9]+): cs:")
1611
    highest = None
1612
    for line in data:
1613
      match = unused_line.match(line)
1614
      if match:
1615
        return int(match.group(1))
1616
      match = used_line.match(line)
1617
      if match:
1618
        minor = int(match.group(1))
1619
        highest = max(highest, minor)
1620
    if highest is None: # there are no minors in use at all
1621
      return 0
1622
    if highest >= cls._MAX_MINORS:
1623
      logger.Error("Error: no free drbd minors!")
1624
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1625
    return highest + 1
1626

    
1627
  @classmethod
1628
  def _IsValidMeta(cls, meta_device):
1629
    """Check if the given meta device looks like a valid one.
1630

1631
    """
1632
    minor = cls._FindUnusedMinor()
1633
    minor_path = cls._DevPath(minor)
1634
    result = utils.RunCmd(["drbdmeta", minor_path,
1635
                           "v08", meta_device, "0",
1636
                           "dstate"])
1637
    if result.failed:
1638
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1639
      return False
1640
    return True
1641

    
1642
  @classmethod
1643
  def _GetShowParser(cls):
1644
    """Return a parser for `drbd show` output.
1645

1646
    This will either create or return an already-create parser for the
1647
    output of the command `drbd show`.
1648

1649
    """
1650
    if cls._PARSE_SHOW is not None:
1651
      return cls._PARSE_SHOW
1652

    
1653
    # pyparsing setup
1654
    lbrace = pyp.Literal("{").suppress()
1655
    rbrace = pyp.Literal("}").suppress()
1656
    semi = pyp.Literal(";").suppress()
1657
    # this also converts the value to an int
1658
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t:(l, [int(t[0])]))
1659

    
1660
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1661
    defa = pyp.Literal("_is_default").suppress()
1662
    dbl_quote = pyp.Literal('"').suppress()
1663

    
1664
    keyword = pyp.Word(pyp.alphanums + '-')
1665

    
1666
    # value types
1667
    value = pyp.Word(pyp.alphanums + '_-/.:')
1668
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1669
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1670
                 number)
1671
    # meta device, extended syntax
1672
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1673
                  number + pyp.Word(']').suppress())
1674

    
1675
    # a statement
1676
    stmt = (~rbrace + keyword + ~lbrace +
1677
            (addr_port ^ value ^ quoted ^ meta_value) +
1678
            pyp.Optional(defa) + semi +
1679
            pyp.Optional(pyp.restOfLine).suppress())
1680

    
1681
    # an entire section
1682
    section_name = pyp.Word(pyp.alphas + '_')
1683
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1684

    
1685
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1686
    bnf.ignore(comment)
1687

    
1688
    cls._PARSE_SHOW = bnf
1689

    
1690
    return bnf
1691

    
1692
  @classmethod
1693
  def _GetDevInfo(cls, minor):
1694
    """Get details about a given DRBD minor.
1695

1696
    This return, if available, the local backing device (as a path)
1697
    and the local and remote (ip, port) information.
1698

1699
    """
1700
    data = {}
1701
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1702
    if result.failed:
1703
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1704
      return data
1705
    out = result.stdout
1706
    if not out:
1707
      return data
1708

    
1709
    bnf = cls._GetShowParser()
1710
    # run pyparse
1711

    
1712
    try:
1713
      results = bnf.parseString(out)
1714
    except pyp.ParseException, err:
1715
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1716
                                    str(err))
1717

    
1718
    # and massage the results into our desired format
1719
    for section in results:
1720
      sname = section[0]
1721
      if sname == "_this_host":
1722
        for lst in section[1:]:
1723
          if lst[0] == "disk":
1724
            data["local_dev"] = lst[1]
1725
          elif lst[0] == "meta-disk":
1726
            data["meta_dev"] = lst[1]
1727
            data["meta_index"] = lst[2]
1728
          elif lst[0] == "address":
1729
            data["local_addr"] = tuple(lst[1:])
1730
      elif sname == "_remote_host":
1731
        for lst in section[1:]:
1732
          if lst[0] == "address":
1733
            data["remote_addr"] = tuple(lst[1:])
1734
    return data
1735

    
1736
  def _MatchesLocal(self, info):
1737
    """Test if our local config matches with an existing device.
1738

1739
    The parameter should be as returned from `_GetDevInfo()`. This
1740
    method tests if our local backing device is the same as the one in
1741
    the info parameter, in effect testing if we look like the given
1742
    device.
1743

1744
    """
1745
    backend = self._children[0]
1746
    if backend is not None:
1747
      retval = (info["local_dev"] == backend.dev_path)
1748
    else:
1749
      retval = ("local_dev" not in info)
1750
    meta = self._children[1]
1751
    if meta is not None:
1752
      retval = retval and (info["meta_dev"] == meta.dev_path)
1753
      retval = retval and (info["meta_index"] == 0)
1754
    else:
1755
      retval = retval and ("meta_dev" not in info and
1756
                           "meta_index" not in info)
1757
    return retval
1758

    
1759
  def _MatchesNet(self, info):
1760
    """Test if our network config matches with an existing device.
1761

1762
    The parameter should be as returned from `_GetDevInfo()`. This
1763
    method tests if our network configuration is the same as the one
1764
    in the info parameter, in effect testing if we look like the given
1765
    device.
1766

1767
    """
1768
    if (((self._lhost is None and not ("local_addr" in info)) and
1769
         (self._rhost is None and not ("remote_addr" in info)))):
1770
      return True
1771

    
1772
    if self._lhost is None:
1773
      return False
1774

    
1775
    if not ("local_addr" in info and
1776
            "remote_addr" in info):
1777
      return False
1778

    
1779
    retval = (info["local_addr"] == (self._lhost, self._lport))
1780
    retval = (retval and
1781
              info["remote_addr"] == (self._rhost, self._rport))
1782
    return retval
1783

    
1784
  @classmethod
1785
  def _AssembleLocal(cls, minor, backend, meta):
1786
    """Configure the local part of a DRBD device.
1787

1788
    This is the first thing that must be done on an unconfigured DRBD
1789
    device. And it must be done only once.
1790

1791
    """
1792
    if not cls._IsValidMeta(meta):
1793
      return False
1794
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1795
                           backend, meta, "0", "-e", "detach",
1796
                           "--create-device"])
1797
    if result.failed:
1798
      logger.Error("Can't attach local disk: %s" % result.output)
1799
    return not result.failed
1800

    
1801
  @classmethod
1802
  def _AssembleNet(cls, minor, net_info, protocol,
1803
                   dual_pri=False, hmac=None, secret=None):
1804
    """Configure the network part of the device.
1805

1806
    """
1807
    lhost, lport, rhost, rport = net_info
1808
    args = ["drbdsetup", cls._DevPath(minor), "net",
1809
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1810
            "-A", "discard-zero-changes",
1811
            "-B", "consensus",
1812
            ]
1813
    if dual_pri:
1814
      args.append("-m")
1815
    if hmac and secret:
1816
      args.extend(["-a", hmac, "-x", secret])
1817
    result = utils.RunCmd(args)
1818
    if result.failed:
1819
      logger.Error("Can't setup network for dbrd device: %s" %
1820
                   result.fail_reason)
1821
      return False
1822

    
1823
    timeout = time.time() + 10
1824
    ok = False
1825
    while time.time() < timeout:
1826
      info = cls._GetDevInfo(minor)
1827
      if not "local_addr" in info or not "remote_addr" in info:
1828
        time.sleep(1)
1829
        continue
1830
      if (info["local_addr"] != (lhost, lport) or
1831
          info["remote_addr"] != (rhost, rport)):
1832
        time.sleep(1)
1833
        continue
1834
      ok = True
1835
      break
1836
    if not ok:
1837
      logger.Error("Timeout while configuring network")
1838
      return False
1839
    return True
1840

    
1841
  def SetSyncSpeed(self, kbytes):
1842
    """Set the speed of the DRBD syncer.
1843

1844
    """
1845
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1846
    if self.minor is None:
1847
      logger.Info("Instance not attached to a device")
1848
      return False
1849
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1850
                           kbytes])
1851
    if result.failed:
1852
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1853
    return not result.failed and children_result
1854

    
1855
  def GetSyncStatus(self):
1856
    """Returns the sync status of the device.
1857

1858
    Returns:
1859
     (sync_percent, estimated_time)
1860

1861
    If sync_percent is None, it means all is ok
1862
    If estimated_time is None, it means we can't esimate
1863
    the time needed, otherwise it's the time left in seconds
1864

1865
    """
1866
    if self.minor is None and not self.Attach():
1867
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1868
    proc_info = self._MassageProcData(self._GetProcData())
1869
    if self.minor not in proc_info:
1870
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1871
                                    self.minor)
1872
    line = proc_info[self.minor]
1873
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1874
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1875
    if match:
1876
      sync_percent = float(match.group(1))
1877
      hours = int(match.group(2))
1878
      minutes = int(match.group(3))
1879
      seconds = int(match.group(4))
1880
      est_time = hours * 3600 + minutes * 60 + seconds
1881
    else:
1882
      sync_percent = None
1883
      est_time = None
1884
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1885
    if not match:
1886
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1887
                                    self.minor)
1888
    client_state = match.group(1)
1889
    is_degraded = client_state != "Connected"
1890
    return sync_percent, est_time, is_degraded
1891

    
1892
  def GetStatus(self):
1893
    """Compute the status of the DRBD device
1894

1895
    Note that DRBD devices don't have the STATUS_EXISTING state.
1896

1897
    """
1898
    if self.minor is None and not self.Attach():
1899
      return self.STATUS_UNKNOWN
1900

    
1901
    data = self._GetProcData()
1902
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1903
                       self.minor)
1904
    for line in data:
1905
      mresult = match.match(line)
1906
      if mresult:
1907
        break
1908
    else:
1909
      logger.Error("Can't find myself!")
1910
      return self.STATUS_UNKNOWN
1911

    
1912
    state = mresult.group(2)
1913
    if state == "Primary":
1914
      result = self.STATUS_ONLINE
1915
    else:
1916
      result = self.STATUS_STANDBY
1917

    
1918
    return result
1919

    
1920
  def Open(self, force=False):
1921
    """Make the local state primary.
1922

1923
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1924
    is given. Since this is a pottentialy dangerous operation, the
1925
    force flag should be only given after creation, when it actually
1926
    has to be given.
1927

1928
    """
1929
    if self.minor is None and not self.Attach():
1930
      logger.Error("DRBD cannot attach to a device during open")
1931
      return False
1932
    cmd = ["drbdsetup", self.dev_path, "primary"]
1933
    if force:
1934
      cmd.append("-o")
1935
    result = utils.RunCmd(cmd)
1936
    if result.failed:
1937
      logger.Error("Can't make drbd device primary: %s" % result.output)
1938
      return False
1939
    return True
1940

    
1941
  def Close(self):
1942
    """Make the local state secondary.
1943

1944
    This will, of course, fail if the device is in use.
1945

1946
    """
1947
    if self.minor is None and not self.Attach():
1948
      logger.Info("Instance not attached to a device")
1949
      raise errors.BlockDeviceError("Can't find device")
1950
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1951
    if result.failed:
1952
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1953
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1954

    
1955
  def Attach(self):
1956
    """Find a DRBD device which matches our config and attach to it.
1957

1958
    In case of partially attached (local device matches but no network
1959
    setup), we perform the network attach. If successful, we re-test
1960
    the attach if can return success.
1961

1962
    """
1963
    for minor in self._GetUsedDevs():
1964
      info = self._GetDevInfo(minor)
1965
      match_l = self._MatchesLocal(info)
1966
      match_r = self._MatchesNet(info)
1967
      if match_l and match_r:
1968
        break
1969
      if match_l and not match_r and "local_addr" not in info:
1970
        res_r = self._AssembleNet(minor,
1971
                                  (self._lhost, self._lport,
1972
                                   self._rhost, self._rport),
1973
                                  "C")
1974
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1975
          break
1976
    else:
1977
      minor = None
1978

    
1979
    self._SetFromMinor(minor)
1980
    return minor is not None
1981

    
1982
  def Assemble(self):
1983
    """Assemble the drbd.
1984

1985
    Method:
1986
      - if we have a local backing device, we bind to it by:
1987
        - checking the list of used drbd devices
1988
        - check if the local minor use of any of them is our own device
1989
        - if yes, abort?
1990
        - if not, bind
1991
      - if we have a local/remote net info:
1992
        - redo the local backing device step for the remote device
1993
        - check if any drbd device is using the local port,
1994
          if yes abort
1995
        - check if any remote drbd device is using the remote
1996
          port, if yes abort (for now)
1997
        - bind our net port
1998
        - bind the remote net port
1999

2000
    """
2001
    self.Attach()
2002
    if self.minor is not None:
2003
      logger.Info("Already assembled")
2004
      return True
2005

    
2006
    result = super(DRBD8, self).Assemble()
2007
    if not result:
2008
      return result
2009

    
2010
    minor = self._FindUnusedMinor()
2011
    need_localdev_teardown = False
2012
    if self._children[0]:
2013
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2014
                                   self._children[1].dev_path)
2015
      if not result:
2016
        return False
2017
      need_localdev_teardown = True
2018
    if self._lhost and self._lport and self._rhost and self._rport:
2019
      result = self._AssembleNet(minor,
2020
                                 (self._lhost, self._lport,
2021
                                  self._rhost, self._rport),
2022
                                 "C")
2023
      if not result:
2024
        if need_localdev_teardown:
2025
          # we will ignore failures from this
2026
          logger.Error("net setup failed, tearing down local device")
2027
          self._ShutdownAll(minor)
2028
        return False
2029
    self._SetFromMinor(minor)
2030
    return True
2031

    
2032
  @classmethod
2033
  def _ShutdownAll(cls, minor):
2034
    """Deactivate the device.
2035

2036
    This will, of course, fail if the device is in use.
2037

2038
    """
2039
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2040
    if result.failed:
2041
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2042
    return not result.failed
2043

    
2044
  def Shutdown(self):
2045
    """Shutdown the DRBD device.
2046

2047
    """
2048
    if self.minor is None and not self.Attach():
2049
      logger.Info("DRBD device not attached to a device during Shutdown")
2050
      return True
2051
    if not self._ShutdownAll(self.minor):
2052
      return False
2053
    self.minor = None
2054
    self.dev_path = None
2055
    return True
2056

    
2057
  def Remove(self):
2058
    """Stub remove for DRBD devices.
2059

2060
    """
2061
    return self.Shutdown()
2062

    
2063
  @classmethod
2064
  def Create(cls, unique_id, children, size):
2065
    """Create a new DRBD8 device.
2066

2067
    Since DRBD devices are not created per se, just assembled, this
2068
    function only initializes the metadata.
2069

2070
    """
2071
    if len(children) != 2:
2072
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2073
    meta = children[1]
2074
    meta.Assemble()
2075
    if not meta.Attach():
2076
      raise errors.BlockDeviceError("Can't attach to meta device")
2077
    if not cls._CheckMetaSize(meta.dev_path):
2078
      raise errors.BlockDeviceError("Invalid meta device size")
2079
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2080
    if not cls._IsValidMeta(meta.dev_path):
2081
      raise errors.BlockDeviceError("Cannot initalize meta device")
2082
    return cls(unique_id, children)
2083

    
2084

    
2085
DEV_MAP = {
2086
  constants.LD_LV: LogicalVolume,
2087
  constants.LD_MD_R1: MDRaid1,
2088
  constants.LD_DRBD7: DRBDev,
2089
  constants.LD_DRBD8: DRBD8,
2090
  }
2091

    
2092

    
2093
def FindDevice(dev_type, unique_id, children):
2094
  """Search for an existing, assembled device.
2095

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

2099
  """
2100
  if dev_type not in DEV_MAP:
2101
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2102
  device = DEV_MAP[dev_type](unique_id, children)
2103
  if not device.Attach():
2104
    return None
2105
  return  device
2106

    
2107

    
2108
def AttachOrAssemble(dev_type, unique_id, children):
2109
  """Try to attach or assemble an existing device.
2110

2111
  This will attach to an existing assembled device or will assemble
2112
  the device, as needed, to bring it fully up.
2113

2114
  """
2115
  if dev_type not in DEV_MAP:
2116
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2117
  device = DEV_MAP[dev_type](unique_id, children)
2118
  if not device.Attach():
2119
    device.Assemble()
2120
  if not device.Attach():
2121
    raise errors.BlockDeviceError("Can't find a valid block device for"
2122
                                  " %s/%s/%s" %
2123
                                  (dev_type, unique_id, children))
2124
  return device
2125

    
2126

    
2127
def Create(dev_type, unique_id, children, size):
2128
  """Create a device.
2129

2130
  """
2131
  if dev_type not in DEV_MAP:
2132
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2133
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2134
  return device