Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 8d519422

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].split()[0]
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: %s" %
803
                   (result.fail_reason, result.output))
804
      self.minor = None
805
    else:
806
      self.minor = free_minor
807
    return not result.failed
808

    
809

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

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

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

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

    
829

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

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

    
850

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

854
    Returns:
855
     (sync_percent, estimated_time)
856

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

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

    
889

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

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

896
    """
897
    return True
898

    
899

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

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

906
    """
907
    return True
908

    
909

    
910

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

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

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

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

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

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

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

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

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

    
956

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

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

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

    
976
    return used_devs
977

    
978

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

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

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

    
994

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

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

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

    
1046

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

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

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

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

    
1074

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

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

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

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

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

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

    
1100

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

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

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

    
1127

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

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

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

    
1144

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

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

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

    
1158

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

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

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

    
1171

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

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

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

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

    
1211

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

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

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

    
1223

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

1227
    This sets our minor variable and our dev_path.
1228

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

    
1236

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

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

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

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

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

    
1289

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

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

    
1303

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

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

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

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

    
1331

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

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

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

    
1353

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

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

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

    
1368

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

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

    
1383

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

1387
    Returns:
1388
     (sync_percent, estimated_time)
1389

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

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

    
1421

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

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

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

    
1448

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

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

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

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

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

    
1475
    return result
1476

    
1477

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

1482
    This writes until we get ENOSPC.
1483

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

    
1494

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

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

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

    
1516

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

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

    
1523

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

    
1530

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

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

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

    
1545

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

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

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

    
1564

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

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