Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 1a87dca7

History | View | Annotate | Download (45.7 kB)

1
#!/usr/bin/python
2
#
3

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

    
21

    
22
"""Block device abstraction"""
23

    
24
import re
25
import time
26
import errno
27

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

    
32

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

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

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

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

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

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

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

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

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

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

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

    
95

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

    
103

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

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

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

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

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

    
132

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

136
    """
137
    raise NotImplementedError
138

    
139

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

143
    """
144
    raise NotImplementedError
145

    
146

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

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

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

158
    """
159
    raise NotImplementedError
160

    
161

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

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

169
    """
170
    raise NotImplementedError
171

    
172

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

176
    """
177
    raise NotImplementedError
178

    
179

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

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

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

189
    """
190
    raise NotImplementedError
191

    
192

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

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

200
    """
201
    raise NotImplementedError
202

    
203

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

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

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

    
216

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

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

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

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

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

    
236

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

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

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

    
260

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

264
    Only supported for some device types.
265

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

    
270

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

    
276

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

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

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

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

    
294

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

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

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

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

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

328
    Args:
329
      vg_name: the volume group name
330

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

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

    
353
    return data
354

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

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

    
367
    return not result.failed
368

    
369

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

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

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

    
392

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

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

399
    """
400
    return True
401

    
402

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

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

409
    """
410
    return True
411

    
412

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

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

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

    
439
    return retval
440

    
441

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

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

447
    """
448
    return True
449

    
450

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

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

456
    """
457
    return True
458

    
459

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

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

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

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

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

    
487
    return snap_name
488

    
489

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

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

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

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

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

    
509

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

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

    
519

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

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

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

    
533
    return (minor is not None)
534

    
535

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

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

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

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

    
556
    return used_md
557

    
558

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

563
    Currently only uuid is returned.
564

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

    
581

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

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

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

    
600

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

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

    
613

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

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

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

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

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

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

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

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

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

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

    
691

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

695
    We don't remove the superblock right now. Mark a to do.
696

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

    
701

    
702
  def AddChild(self, device):
703
    """Add a new member to the md raid1.
704

705
    """
706
    if self.minor is None and not self.Attach():
707
      raise errors.BlockDeviceError("Can't attach to device")
708
    if device.dev_path is None:
709
      raise errors.BlockDeviceError("New child is not initialised")
710
    result = utils.RunCmd(["mdadm", "-a", self.dev_path, device.dev_path])
711
    if result.failed:
712
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
713
                                    result.output)
714
    new_len = len(self._children) + 1
715
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
716
    if result.failed:
717
      raise errors.BlockDeviceError("Can't grow md array: %s" %
718
                                    result.output)
719
    self._children.append(device)
720

    
721

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

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

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

    
756

    
757
  def GetStatus(self):
758
    """Return the status of the device.
759

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

    
768

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

772
    This sets our minor variable and our dev_path.
773

774
    """
775
    self.minor = minor
776
    self.dev_path = "/dev/md%d" % minor
777

    
778

    
779
  def Assemble(self):
780
    """Assemble the MD device.
781

782
    At this point we should have:
783
      - list of children devices
784
      - uuid
785

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

    
808

    
809
  def Shutdown(self):
810
    """Tear down the MD array.
811

812
    This does a 'mdadm --stop' so after this command, the array is no
813
    longer available.
814

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

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

    
828

    
829
  def SetSyncSpeed(self, kbytes):
830
    """Set the maximum sync speed for the MD array.
831

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

    
849

    
850
  def GetSyncStatus(self):
851
    """Returns the sync status of the device.
852

853
    Returns:
854
     (sync_percent, estimated_time)
855

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

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

    
888

    
889
  def Open(self, force=False):
890
    """Make the device ready for I/O.
891

892
    This is a no-op for the MDRaid1 device type, although we could use
893
    the 2.6.18's new array_state thing.
894

895
    """
896
    return True
897

    
898

    
899
  def Close(self):
900
    """Notifies that the device will no longer be used for I/O.
901

902
    This is a no-op for the MDRaid1 device type, but see comment for
903
    `Open()`.
904

905
    """
906
    return True
907

    
908

    
909

    
910
class DRBDev(BlockDev):
911
  """DRBD block device.
912

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

917
  The unique_id for the drbd device is the (local_ip, local_port,
918
  remote_ip, remote_port) tuple, and it must have two children: the
919
  data device and the meta_device. The meta device is checked for
920
  valid size and is zeroed on create.
921

922
  """
923
  _DRBD_MAJOR = 147
924
  _ST_UNCONFIGURED = "Unconfigured"
925
  _ST_WFCONNECTION = "WFConnection"
926
  _ST_CONNECTED = "Connected"
927

    
928
  def __init__(self, unique_id, children):
929
    super(DRBDev, self).__init__(unique_id, children)
930
    self.major = self._DRBD_MAJOR
931
    if len(children) != 2:
932
      raise ValueError("Invalid configuration data %s" % str(children))
933
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
934
      raise ValueError("Invalid configuration data %s" % str(unique_id))
935
    self._lhost, self._lport, self._rhost, self._rport = unique_id
936
    self.Attach()
937

    
938
  @staticmethod
939
  def _DevPath(minor):
940
    """Return the path to a drbd device for a given minor.
941

942
    """
943
    return "/dev/drbd%d" % minor
944

    
945
  @staticmethod
946
  def _GetProcData():
947
    """Return data from /proc/drbd.
948

949
    """
950
    stat = open("/proc/drbd", "r")
951
    data = stat.read().splitlines()
952
    stat.close()
953
    return data
954

    
955

    
956
  @classmethod
957
  def _GetUsedDevs(cls):
958
    """Compute the list of used DRBD devices.
959

960
    """
961
    data = cls._GetProcData()
962

    
963
    used_devs = {}
964
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
965
    for line in data:
966
      match = valid_line.match(line)
967
      if not match:
968
        continue
969
      minor = int(match.group(1))
970
      state = match.group(2)
971
      if state == cls._ST_UNCONFIGURED:
972
        continue
973
      used_devs[minor] = state, line
974

    
975
    return used_devs
976

    
977

    
978
  @classmethod
979
  def _FindUnusedMinor(cls):
980
    """Find an unused DRBD device.
981

982
    """
983
    data = cls._GetProcData()
984

    
985
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
986
    for line in data:
987
      match = valid_line.match(line)
988
      if match:
989
        return int(match.group(1))
990
    logger.Error("Error: no free drbd minors!")
991
    return None
992

    
993

    
994
  @classmethod
995
  def _GetDevInfo(cls, minor):
996
    """Get details about a given DRBD minor.
997

998
    This return, if available, the local backing device in (major,
999
    minor) formant and the local and remote (ip, port) information.
1000

1001
    """
1002
    data = {}
1003
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1004
    if result.failed:
1005
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1006
      return data
1007
    out = result.stdout
1008
    if out == "Not configured\n":
1009
      return data
1010
    for line in out.splitlines():
1011
      if "local_dev" not in data:
1012
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1013
        if match:
1014
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1015
          continue
1016
      if "meta_dev" not in data:
1017
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1018
        if match:
1019
          if match.group(2) is not None and match.group(3) is not None:
1020
            # matched on the major/minor
1021
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1022
          else:
1023
            # matched on the "internal" string
1024
            data["meta_dev"] = match.group(1)
1025
            # in this case, no meta_index is in the output
1026
            data["meta_index"] = -1
1027
          continue
1028
      if "meta_index" not in data:
1029
        match = re.match("^Meta index: ([0-9]+).*$", line)
1030
        if match:
1031
          data["meta_index"] = int(match.group(1))
1032
          continue
1033
      if "local_addr" not in data:
1034
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1035
        if match:
1036
          data["local_addr"] = (match.group(1), int(match.group(2)))
1037
          continue
1038
      if "remote_addr" not in data:
1039
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1040
        if match:
1041
          data["remote_addr"] = (match.group(1), int(match.group(2)))
1042
          continue
1043
    return data
1044

    
1045

    
1046
  def _MatchesLocal(self, info):
1047
    """Test if our local config matches with an existing device.
1048

1049
    The parameter should be as returned from `_GetDevInfo()`. This
1050
    method tests if our local backing device is the same as the one in
1051
    the info parameter, in effect testing if we look like the given
1052
    device.
1053

1054
    """
1055
    if not ("local_dev" in info and "meta_dev" in info and
1056
            "meta_index" in info):
1057
      return False
1058

    
1059
    backend = self._children[0]
1060
    if backend is not None:
1061
      retval = (info["local_dev"] == (backend.major, backend.minor))
1062
    else:
1063
      retval = (info["local_dev"] == (0, 0))
1064
    meta = self._children[1]
1065
    if meta is not None:
1066
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1067
      retval = retval and (info["meta_index"] == 0)
1068
    else:
1069
      retval = retval and (info["meta_dev"] == "internal" and
1070
                           info["meta_index"] == -1)
1071
    return retval
1072

    
1073

    
1074
  def _MatchesNet(self, info):
1075
    """Test if our network config matches with an existing device.
1076

1077
    The parameter should be as returned from `_GetDevInfo()`. This
1078
    method tests if our network configuration is the same as the one
1079
    in the info parameter, in effect testing if we look like the given
1080
    device.
1081

1082
    """
1083
    if (((self._lhost is None and not ("local_addr" in info)) and
1084
         (self._rhost is None and not ("remote_addr" in info)))):
1085
      return True
1086

    
1087
    if self._lhost is None:
1088
      return False
1089

    
1090
    if not ("local_addr" in info and
1091
            "remote_addr" in info):
1092
      return False
1093

    
1094
    retval = (info["local_addr"] == (self._lhost, self._lport))
1095
    retval = (retval and
1096
              info["remote_addr"] == (self._rhost, self._rport))
1097
    return retval
1098

    
1099

    
1100
  @staticmethod
1101
  def _IsValidMeta(meta_device):
1102
    """Check if the given meta device looks like a valid one.
1103

1104
    This currently only check the size, which must be around
1105
    128MiB.
1106

1107
    """
1108
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1109
    if result.failed:
1110
      logger.Error("Failed to get device size: %s" % result.fail_reason)
1111
      return False
1112
    try:
1113
      sectors = int(result.stdout)
1114
    except ValueError:
1115
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1116
      return False
1117
    bytes = sectors * 512
1118
    if bytes < 128*1024*1024: # less than 128MiB
1119
      logger.Error("Meta device too small (%.2fMib)" % (bytes/1024/1024))
1120
      return False
1121
    if bytes > (128+32)*1024*1024: # account for an extra (big) PE on LVM
1122
      logger.Error("Meta device too big (%.2fMiB)" % (bytes/1024/1024))
1123
      return False
1124
    return True
1125

    
1126

    
1127
  @classmethod
1128
  def _AssembleLocal(cls, minor, backend, meta):
1129
    """Configure the local part of a DRBD device.
1130

1131
    This is the first thing that must be done on an unconfigured DRBD
1132
    device. And it must be done only once.
1133

1134
    """
1135
    if not cls._IsValidMeta(meta):
1136
      return False
1137
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1138
                           backend, meta, "0", "-e", "detach"])
1139
    if result.failed:
1140
      logger.Error("Can't attach local disk: %s" % result.output)
1141
    return not result.failed
1142

    
1143

    
1144
  @classmethod
1145
  def _ShutdownLocal(cls, minor):
1146
    """Detach from the local device.
1147

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

1151
    """
1152
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1153
    if result.failed:
1154
      logger.Error("Can't detach local device: %s" % result.output)
1155
    return not result.failed
1156

    
1157

    
1158
  @staticmethod
1159
  def _ShutdownAll(minor):
1160
    """Deactivate the device.
1161

1162
    This will, of course, fail if the device is in use.
1163

1164
    """
1165
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1166
    if result.failed:
1167
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1168
    return not result.failed
1169

    
1170

    
1171
  @classmethod
1172
  def _AssembleNet(cls, minor, net_info, protocol):
1173
    """Configure the network part of the device.
1174

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

1182
    """
1183
    lhost, lport, rhost, rport = net_info
1184
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1185
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1186
                           protocol])
1187
    if result.failed:
1188
      logger.Error("Can't setup network for dbrd device: %s" %
1189
                   result.fail_reason)
1190
      return False
1191

    
1192
    timeout = time.time() + 10
1193
    ok = False
1194
    while time.time() < timeout:
1195
      info = cls._GetDevInfo(minor)
1196
      if not "local_addr" in info or not "remote_addr" in info:
1197
        time.sleep(1)
1198
        continue
1199
      if (info["local_addr"] != (lhost, lport) or
1200
          info["remote_addr"] != (rhost, rport)):
1201
        time.sleep(1)
1202
        continue
1203
      ok = True
1204
      break
1205
    if not ok:
1206
      logger.Error("Timeout while configuring network")
1207
      return False
1208
    return True
1209

    
1210

    
1211
  @classmethod
1212
  def _ShutdownNet(cls, minor):
1213
    """Disconnect from the remote peer.
1214

1215
    This fails if we don't have a local device.
1216

1217
    """
1218
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1219
    logger.Error("Can't shutdown network: %s" % result.output)
1220
    return not result.failed
1221

    
1222

    
1223
  def _SetFromMinor(self, minor):
1224
    """Set our parameters based on the given minor.
1225

1226
    This sets our minor variable and our dev_path.
1227

1228
    """
1229
    if minor is None:
1230
      self.minor = self.dev_path = None
1231
    else:
1232
      self.minor = minor
1233
      self.dev_path = self._DevPath(minor)
1234

    
1235

    
1236
  def Assemble(self):
1237
    """Assemble the drbd.
1238

1239
    Method:
1240
      - if we have a local backing device, we bind to it by:
1241
        - checking the list of used drbd devices
1242
        - check if the local minor use of any of them is our own device
1243
        - if yes, abort?
1244
        - if not, bind
1245
      - if we have a local/remote net info:
1246
        - redo the local backing device step for the remote device
1247
        - check if any drbd device is using the local port,
1248
          if yes abort
1249
        - check if any remote drbd device is using the remote
1250
          port, if yes abort (for now)
1251
        - bind our net port
1252
        - bind the remote net port
1253

1254
    """
1255
    self.Attach()
1256
    if self.minor is not None:
1257
      logger.Info("Already assembled")
1258
      return True
1259

    
1260
    result = super(DRBDev, self).Assemble()
1261
    if not result:
1262
      return result
1263

    
1264
    minor = self._FindUnusedMinor()
1265
    if minor is None:
1266
      raise errors.BlockDeviceError("Not enough free minors for DRBD!")
1267
    need_localdev_teardown = False
1268
    if self._children[0]:
1269
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1270
                                   self._children[1].dev_path)
1271
      if not result:
1272
        return False
1273
      need_localdev_teardown = True
1274
    if self._lhost and self._lport and self._rhost and self._rport:
1275
      result = self._AssembleNet(minor,
1276
                                 (self._lhost, self._lport,
1277
                                  self._rhost, self._rport),
1278
                                 "C")
1279
      if not result:
1280
        if need_localdev_teardown:
1281
          # we will ignore failures from this
1282
          logger.Error("net setup failed, tearing down local device")
1283
          self._ShutdownAll(minor)
1284
        return False
1285
    self._SetFromMinor(minor)
1286
    return True
1287

    
1288

    
1289
  def Shutdown(self):
1290
    """Shutdown the DRBD device.
1291

1292
    """
1293
    if self.minor is None and not self.Attach():
1294
      logger.Info("DRBD device not attached to a device during Shutdown")
1295
      return True
1296
    if not self._ShutdownAll(self.minor):
1297
      return False
1298
    self.minor = None
1299
    self.dev_path = None
1300
    return True
1301

    
1302

    
1303
  def Attach(self):
1304
    """Find a DRBD device which matches our config and attach to it.
1305

1306
    In case of partially attached (local device matches but no network
1307
    setup), we perform the network attach. If successful, we re-test
1308
    the attach if can return success.
1309

1310
    """
1311
    for minor in self._GetUsedDevs():
1312
      info = self._GetDevInfo(minor)
1313
      match_l = self._MatchesLocal(info)
1314
      match_r = self._MatchesNet(info)
1315
      if match_l and match_r:
1316
        break
1317
      if match_l and not match_r and "local_addr" not in info:
1318
        res_r = self._AssembleNet(minor,
1319
                                  (self._lhost, self._lport,
1320
                                   self._rhost, self._rport),
1321
                                  "C")
1322
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1323
          break
1324
    else:
1325
      minor = None
1326

    
1327
    self._SetFromMinor(minor)
1328
    return minor is not None
1329

    
1330

    
1331
  def Open(self, force=False):
1332
    """Make the local state primary.
1333

1334
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1335
    is given. Since this is a pottentialy dangerous operation, the
1336
    force flag should be only given after creation, when it actually
1337
    has to be given.
1338

1339
    """
1340
    if self.minor is None and not self.Attach():
1341
      logger.Error("DRBD cannot attach to a device during open")
1342
      return False
1343
    cmd = ["drbdsetup", self.dev_path, "primary"]
1344
    if force:
1345
      cmd.append("--do-what-I-say")
1346
    result = utils.RunCmd(cmd)
1347
    if result.failed:
1348
      logger.Error("Can't make drbd device primary: %s" % result.output)
1349
      return False
1350
    return True
1351

    
1352

    
1353
  def Close(self):
1354
    """Make the local state secondary.
1355

1356
    This will, of course, fail if the device is in use.
1357

1358
    """
1359
    if self.minor is None and not self.Attach():
1360
      logger.Info("Instance not attached to a device")
1361
      raise errors.BlockDeviceError("Can't find device")
1362
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1363
    if result.failed:
1364
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1365
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1366

    
1367

    
1368
  def SetSyncSpeed(self, kbytes):
1369
    """Set the speed of the DRBD syncer.
1370

1371
    """
1372
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1373
    if self.minor is None:
1374
      logger.Info("Instance not attached to a device")
1375
      return False
1376
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1377
                           kbytes])
1378
    if result.failed:
1379
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1380
    return not result.failed and children_result
1381

    
1382

    
1383
  def GetSyncStatus(self):
1384
    """Returns the sync status of the device.
1385

1386
    Returns:
1387
     (sync_percent, estimated_time)
1388

1389
    If sync_percent is None, it means all is ok
1390
    If estimated_time is None, it means we can't esimate
1391
    the time needed, otherwise it's the time left in seconds
1392

1393
    """
1394
    if self.minor is None and not self.Attach():
1395
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1396
    proc_info = self._MassageProcData(self._GetProcData())
1397
    if self.minor not in proc_info:
1398
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1399
                                    self.minor)
1400
    line = proc_info[self.minor]
1401
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1402
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1403
    if match:
1404
      sync_percent = float(match.group(1))
1405
      hours = int(match.group(2))
1406
      minutes = int(match.group(3))
1407
      seconds = int(match.group(4))
1408
      est_time = hours * 3600 + minutes * 60 + seconds
1409
    else:
1410
      sync_percent = None
1411
      est_time = None
1412
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1413
    if not match:
1414
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1415
                                    self.minor)
1416
    client_state = match.group(1)
1417
    is_degraded = client_state != "Connected"
1418
    return sync_percent, est_time, is_degraded
1419

    
1420

    
1421
  @staticmethod
1422
  def _MassageProcData(data):
1423
    """Transform the output of _GetProdData into a nicer form.
1424

1425
    Returns:
1426
      a dictionary of minor: joined lines from /proc/drbd for that minor
1427

1428
    """
1429
    lmatch = re.compile("^ *([0-9]+):.*$")
1430
    results = {}
1431
    old_minor = old_line = None
1432
    for line in data:
1433
      lresult = lmatch.match(line)
1434
      if lresult is not None:
1435
        if old_minor is not None:
1436
          results[old_minor] = old_line
1437
        old_minor = int(lresult.group(1))
1438
        old_line = line
1439
      else:
1440
        if old_minor is not None:
1441
          old_line += " " + line.strip()
1442
    # add last line
1443
    if old_minor is not None:
1444
      results[old_minor] = old_line
1445
    return results
1446

    
1447

    
1448
  def GetStatus(self):
1449
    """Compute the status of the DRBD device
1450

1451
    Note that DRBD devices don't have the STATUS_EXISTING state.
1452

1453
    """
1454
    if self.minor is None and not self.Attach():
1455
      return self.STATUS_UNKNOWN
1456

    
1457
    data = self._GetProcData()
1458
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1459
                       self.minor)
1460
    for line in data:
1461
      mresult = match.match(line)
1462
      if mresult:
1463
        break
1464
    else:
1465
      logger.Error("Can't find myself!")
1466
      return self.STATUS_UNKNOWN
1467

    
1468
    state = mresult.group(2)
1469
    if state == "Primary":
1470
      result = self.STATUS_ONLINE
1471
    else:
1472
      result = self.STATUS_STANDBY
1473

    
1474
    return result
1475

    
1476

    
1477
  @staticmethod
1478
  def _ZeroDevice(device):
1479
    """Zero a device.
1480

1481
    This writes until we get ENOSPC.
1482

1483
    """
1484
    f = open(device, "w")
1485
    buf = "\0" * 1048576
1486
    try:
1487
      while True:
1488
        f.write(buf)
1489
    except IOError, err:
1490
      if err.errno != errno.ENOSPC:
1491
        raise
1492

    
1493

    
1494
  @classmethod
1495
  def Create(cls, unique_id, children, size):
1496
    """Create a new DRBD device.
1497

1498
    Since DRBD devices are not created per se, just assembled, this
1499
    function just zeroes the meta device.
1500

1501
    """
1502
    if len(children) != 2:
1503
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1504
    meta = children[1]
1505
    meta.Assemble()
1506
    if not meta.Attach():
1507
      raise errors.BlockDeviceError("Can't attach to meta device")
1508
    if not cls._IsValidMeta(meta.dev_path):
1509
      raise errors.BlockDeviceError("Invalid meta device")
1510
    logger.Info("Started zeroing device %s" % meta.dev_path)
1511
    cls._ZeroDevice(meta.dev_path)
1512
    logger.Info("Done zeroing device %s" % meta.dev_path)
1513
    return cls(unique_id, children)
1514

    
1515

    
1516
  def Remove(self):
1517
    """Stub remove for DRBD devices.
1518

1519
    """
1520
    return self.Shutdown()
1521

    
1522

    
1523
DEV_MAP = {
1524
  "lvm": LogicalVolume,
1525
  "md_raid1": MDRaid1,
1526
  "drbd": DRBDev,
1527
  }
1528

    
1529

    
1530
def FindDevice(dev_type, unique_id, children):
1531
  """Search for an existing, assembled device.
1532

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

1536
  """
1537
  if dev_type not in DEV_MAP:
1538
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1539
  device = DEV_MAP[dev_type](unique_id, children)
1540
  if not device.Attach():
1541
    return None
1542
  return  device
1543

    
1544

    
1545
def AttachOrAssemble(dev_type, unique_id, children):
1546
  """Try to attach or assemble an existing device.
1547

1548
  This will attach to an existing assembled device or will assemble
1549
  the device, as needed, to bring it fully up.
1550

1551
  """
1552
  if dev_type not in DEV_MAP:
1553
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1554
  device = DEV_MAP[dev_type](unique_id, children)
1555
  if not device.Attach():
1556
    device.Assemble()
1557
  if not device.Attach():
1558
    raise errors.BlockDeviceError("Can't find a valid block device for"
1559
                                  " %s/%s/%s" %
1560
                                  (dev_type, unique_id, children))
1561
  return device
1562

    
1563

    
1564
def Create(dev_type, unique_id, children, size):
1565
  """Create a device.
1566

1567
  """
1568
  if dev_type not in DEV_MAP:
1569
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1570
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
1571
  return device