Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 0f7f32d9

History | View | Annotate | Download (46.9 kB)

1
#
2
#
3

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

    
21

    
22
"""Block device abstraction"""
23

    
24
import re
25
import time
26
import errno
27

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

    
33

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

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

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

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

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

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

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

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

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

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

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

    
96

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

    
104

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

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

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

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

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

    
133

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

137
    """
138
    raise NotImplementedError
139

    
140

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

144
    """
145
    raise NotImplementedError
146

    
147

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

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

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

159
    """
160
    raise NotImplementedError
161

    
162

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

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

170
    """
171
    raise NotImplementedError
172

    
173

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

177
    """
178
    raise NotImplementedError
179

    
180

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

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

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

190
    """
191
    raise NotImplementedError
192

    
193

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

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

201
    """
202
    raise NotImplementedError
203

    
204

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

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

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

    
217

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

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

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

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

234
    """
235
    return None, None, False
236

    
237

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

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

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

    
261

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

265
    Only supported for some device types.
266

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

    
271

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

    
277

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

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

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

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

    
295

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

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

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

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

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

329
    Args:
330
      vg_name: the volume group name
331

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

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

    
354
    return data
355

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

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

    
368
    return not result.failed
369

    
370

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

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

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

    
393

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

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

400
    """
401
    return True
402

    
403

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

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

410
    """
411
    return True
412

    
413

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

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

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

    
440
    return retval
441

    
442

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

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

448
    """
449
    return True
450

    
451

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

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

457
    """
458
    return True
459

    
460

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

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

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

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

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

    
488
    return snap_name
489

    
490

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

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

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

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

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

    
510

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

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

    
520

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

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

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

    
534
    return (minor is not None)
535

    
536

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

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

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

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

    
557
    return used_md
558

    
559

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

564
    Currently only uuid is returned.
565

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

    
582

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

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

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

    
601

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

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

    
614

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

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

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

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

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

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

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

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

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

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

    
692

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

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

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

    
702

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

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

    
722

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

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

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

    
757

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

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

    
769

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

773
    This sets our minor variable and our dev_path.
774

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

    
779

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

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

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

    
810

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

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

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

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

    
830

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

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

    
851

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

855
    Returns:
856
     (sync_percent, estimated_time)
857

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

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

    
890

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

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

897
    """
898
    return True
899

    
900

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

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

907
    """
908
    return True
909

    
910

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

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

917
  """
918
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
919
                           r" \(api:(\d+)/proto:(\d+)\)")
920
  _DRBD_KVER = 0
921

    
922
  @staticmethod
923
  def _GetProcData():
924
    """Return data from /proc/drbd.
925

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

    
936
  @classmethod
937
  def _GetVersion(cls):
938
    """Return the DRBD version.
939

940
    This will return a list [k_major, k_minor, k_point, api, proto].
941

942
    """
943
    proc_data = cls._GetProcData()
944
    first_line = proc_data[0].strip()
945
    version = cls._VERSION_RE.match(first_line)
946
    if not version:
947
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
948
                                    first_line)
949
    return [int(val) for val in version.groups()]
950

    
951

    
952
class DRBDev(BaseDRBD):
953
  """DRBD block device.
954

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

959
  The unique_id for the drbd device is the (local_ip, local_port,
960
  remote_ip, remote_port) tuple, and it must have two children: the
961
  data device and the meta_device. The meta device is checked for
962
  valid size and is zeroed on create.
963

964
  """
965
  _DRBD_MAJOR = 147
966
  _ST_UNCONFIGURED = "Unconfigured"
967
  _ST_WFCONNECTION = "WFConnection"
968
  _ST_CONNECTED = "Connected"
969

    
970
  def __init__(self, unique_id, children):
971
    super(DRBDev, self).__init__(unique_id, children)
972
    self.major = self._DRBD_MAJOR
973
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
974
    if kmaj != 0 and kmin != 7:
975
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
976
                                    " requested ganeti usage: kernel is"
977
                                    " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
978

    
979
    if len(children) != 2:
980
      raise ValueError("Invalid configuration data %s" % str(children))
981
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
982
      raise ValueError("Invalid configuration data %s" % str(unique_id))
983
    self._lhost, self._lport, self._rhost, self._rport = unique_id
984
    self.Attach()
985

    
986
  @staticmethod
987
  def _DevPath(minor):
988
    """Return the path to a drbd device for a given minor.
989

990
    """
991
    return "/dev/drbd%d" % minor
992

    
993
  @classmethod
994
  def _GetUsedDevs(cls):
995
    """Compute the list of used DRBD devices.
996

997
    """
998
    data = cls._GetProcData()
999

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

    
1012
    return used_devs
1013

    
1014

    
1015
  @classmethod
1016
  def _FindUnusedMinor(cls):
1017
    """Find an unused DRBD device.
1018

1019
    """
1020
    data = cls._GetProcData()
1021

    
1022
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1023
    for line in data:
1024
      match = valid_line.match(line)
1025
      if match:
1026
        return int(match.group(1))
1027
    logger.Error("Error: no free drbd minors!")
1028
    return None
1029

    
1030

    
1031
  @classmethod
1032
  def _GetDevInfo(cls, minor):
1033
    """Get details about a given DRBD minor.
1034

1035
    This return, if available, the local backing device in (major,
1036
    minor) formant and the local and remote (ip, port) information.
1037

1038
    """
1039
    data = {}
1040
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1041
    if result.failed:
1042
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1043
      return data
1044
    out = result.stdout
1045
    if out == "Not configured\n":
1046
      return data
1047
    for line in out.splitlines():
1048
      if "local_dev" not in data:
1049
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1050
        if match:
1051
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1052
          continue
1053
      if "meta_dev" not in data:
1054
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1055
        if match:
1056
          if match.group(2) is not None and match.group(3) is not None:
1057
            # matched on the major/minor
1058
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1059
          else:
1060
            # matched on the "internal" string
1061
            data["meta_dev"] = match.group(1)
1062
            # in this case, no meta_index is in the output
1063
            data["meta_index"] = -1
1064
          continue
1065
      if "meta_index" not in data:
1066
        match = re.match("^Meta index: ([0-9]+).*$", line)
1067
        if match:
1068
          data["meta_index"] = int(match.group(1))
1069
          continue
1070
      if "local_addr" not in data:
1071
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1072
        if match:
1073
          data["local_addr"] = (match.group(1), int(match.group(2)))
1074
          continue
1075
      if "remote_addr" not in data:
1076
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1077
        if match:
1078
          data["remote_addr"] = (match.group(1), int(match.group(2)))
1079
          continue
1080
    return data
1081

    
1082

    
1083
  def _MatchesLocal(self, info):
1084
    """Test if our local config matches with an existing device.
1085

1086
    The parameter should be as returned from `_GetDevInfo()`. This
1087
    method tests if our local backing device is the same as the one in
1088
    the info parameter, in effect testing if we look like the given
1089
    device.
1090

1091
    """
1092
    if not ("local_dev" in info and "meta_dev" in info and
1093
            "meta_index" in info):
1094
      return False
1095

    
1096
    backend = self._children[0]
1097
    if backend is not None:
1098
      retval = (info["local_dev"] == (backend.major, backend.minor))
1099
    else:
1100
      retval = (info["local_dev"] == (0, 0))
1101
    meta = self._children[1]
1102
    if meta is not None:
1103
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1104
      retval = retval and (info["meta_index"] == 0)
1105
    else:
1106
      retval = retval and (info["meta_dev"] == "internal" and
1107
                           info["meta_index"] == -1)
1108
    return retval
1109

    
1110

    
1111
  def _MatchesNet(self, info):
1112
    """Test if our network config matches with an existing device.
1113

1114
    The parameter should be as returned from `_GetDevInfo()`. This
1115
    method tests if our network configuration is the same as the one
1116
    in the info parameter, in effect testing if we look like the given
1117
    device.
1118

1119
    """
1120
    if (((self._lhost is None and not ("local_addr" in info)) and
1121
         (self._rhost is None and not ("remote_addr" in info)))):
1122
      return True
1123

    
1124
    if self._lhost is None:
1125
      return False
1126

    
1127
    if not ("local_addr" in info and
1128
            "remote_addr" in info):
1129
      return False
1130

    
1131
    retval = (info["local_addr"] == (self._lhost, self._lport))
1132
    retval = (retval and
1133
              info["remote_addr"] == (self._rhost, self._rport))
1134
    return retval
1135

    
1136

    
1137
  @staticmethod
1138
  def _IsValidMeta(meta_device):
1139
    """Check if the given meta device looks like a valid one.
1140

1141
    This currently only check the size, which must be around
1142
    128MiB.
1143

1144
    """
1145
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1146
    if result.failed:
1147
      logger.Error("Failed to get device size: %s" % result.fail_reason)
1148
      return False
1149
    try:
1150
      sectors = int(result.stdout)
1151
    except ValueError:
1152
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1153
      return False
1154
    bytes = sectors * 512
1155
    if bytes < 128*1024*1024: # less than 128MiB
1156
      logger.Error("Meta device too small (%.2fMib)" % (bytes/1024/1024))
1157
      return False
1158
    if bytes > (128+32)*1024*1024: # account for an extra (big) PE on LVM
1159
      logger.Error("Meta device too big (%.2fMiB)" % (bytes/1024/1024))
1160
      return False
1161
    return True
1162

    
1163

    
1164
  @classmethod
1165
  def _AssembleLocal(cls, minor, backend, meta):
1166
    """Configure the local part of a DRBD device.
1167

1168
    This is the first thing that must be done on an unconfigured DRBD
1169
    device. And it must be done only once.
1170

1171
    """
1172
    if not cls._IsValidMeta(meta):
1173
      return False
1174
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1175
                           backend, meta, "0", "-e", "detach"])
1176
    if result.failed:
1177
      logger.Error("Can't attach local disk: %s" % result.output)
1178
    return not result.failed
1179

    
1180

    
1181
  @classmethod
1182
  def _ShutdownLocal(cls, minor):
1183
    """Detach from the local device.
1184

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

1188
    """
1189
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1190
    if result.failed:
1191
      logger.Error("Can't detach local device: %s" % result.output)
1192
    return not result.failed
1193

    
1194

    
1195
  @staticmethod
1196
  def _ShutdownAll(minor):
1197
    """Deactivate the device.
1198

1199
    This will, of course, fail if the device is in use.
1200

1201
    """
1202
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1203
    if result.failed:
1204
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1205
    return not result.failed
1206

    
1207

    
1208
  @classmethod
1209
  def _AssembleNet(cls, minor, net_info, protocol):
1210
    """Configure the network part of the device.
1211

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

1219
    """
1220
    lhost, lport, rhost, rport = net_info
1221
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1222
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1223
                           protocol])
1224
    if result.failed:
1225
      logger.Error("Can't setup network for dbrd device: %s" %
1226
                   result.fail_reason)
1227
      return False
1228

    
1229
    timeout = time.time() + 10
1230
    ok = False
1231
    while time.time() < timeout:
1232
      info = cls._GetDevInfo(minor)
1233
      if not "local_addr" in info or not "remote_addr" in info:
1234
        time.sleep(1)
1235
        continue
1236
      if (info["local_addr"] != (lhost, lport) or
1237
          info["remote_addr"] != (rhost, rport)):
1238
        time.sleep(1)
1239
        continue
1240
      ok = True
1241
      break
1242
    if not ok:
1243
      logger.Error("Timeout while configuring network")
1244
      return False
1245
    return True
1246

    
1247

    
1248
  @classmethod
1249
  def _ShutdownNet(cls, minor):
1250
    """Disconnect from the remote peer.
1251

1252
    This fails if we don't have a local device.
1253

1254
    """
1255
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1256
    logger.Error("Can't shutdown network: %s" % result.output)
1257
    return not result.failed
1258

    
1259

    
1260
  def _SetFromMinor(self, minor):
1261
    """Set our parameters based on the given minor.
1262

1263
    This sets our minor variable and our dev_path.
1264

1265
    """
1266
    if minor is None:
1267
      self.minor = self.dev_path = None
1268
    else:
1269
      self.minor = minor
1270
      self.dev_path = self._DevPath(minor)
1271

    
1272

    
1273
  def Assemble(self):
1274
    """Assemble the drbd.
1275

1276
    Method:
1277
      - if we have a local backing device, we bind to it by:
1278
        - checking the list of used drbd devices
1279
        - check if the local minor use of any of them is our own device
1280
        - if yes, abort?
1281
        - if not, bind
1282
      - if we have a local/remote net info:
1283
        - redo the local backing device step for the remote device
1284
        - check if any drbd device is using the local port,
1285
          if yes abort
1286
        - check if any remote drbd device is using the remote
1287
          port, if yes abort (for now)
1288
        - bind our net port
1289
        - bind the remote net port
1290

1291
    """
1292
    self.Attach()
1293
    if self.minor is not None:
1294
      logger.Info("Already assembled")
1295
      return True
1296

    
1297
    result = super(DRBDev, self).Assemble()
1298
    if not result:
1299
      return result
1300

    
1301
    minor = self._FindUnusedMinor()
1302
    if minor is None:
1303
      raise errors.BlockDeviceError("Not enough free minors for DRBD!")
1304
    need_localdev_teardown = False
1305
    if self._children[0]:
1306
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1307
                                   self._children[1].dev_path)
1308
      if not result:
1309
        return False
1310
      need_localdev_teardown = True
1311
    if self._lhost and self._lport and self._rhost and self._rport:
1312
      result = self._AssembleNet(minor,
1313
                                 (self._lhost, self._lport,
1314
                                  self._rhost, self._rport),
1315
                                 "C")
1316
      if not result:
1317
        if need_localdev_teardown:
1318
          # we will ignore failures from this
1319
          logger.Error("net setup failed, tearing down local device")
1320
          self._ShutdownAll(minor)
1321
        return False
1322
    self._SetFromMinor(minor)
1323
    return True
1324

    
1325

    
1326
  def Shutdown(self):
1327
    """Shutdown the DRBD device.
1328

1329
    """
1330
    if self.minor is None and not self.Attach():
1331
      logger.Info("DRBD device not attached to a device during Shutdown")
1332
      return True
1333
    if not self._ShutdownAll(self.minor):
1334
      return False
1335
    self.minor = None
1336
    self.dev_path = None
1337
    return True
1338

    
1339

    
1340
  def Attach(self):
1341
    """Find a DRBD device which matches our config and attach to it.
1342

1343
    In case of partially attached (local device matches but no network
1344
    setup), we perform the network attach. If successful, we re-test
1345
    the attach if can return success.
1346

1347
    """
1348
    for minor in self._GetUsedDevs():
1349
      info = self._GetDevInfo(minor)
1350
      match_l = self._MatchesLocal(info)
1351
      match_r = self._MatchesNet(info)
1352
      if match_l and match_r:
1353
        break
1354
      if match_l and not match_r and "local_addr" not in info:
1355
        res_r = self._AssembleNet(minor,
1356
                                  (self._lhost, self._lport,
1357
                                   self._rhost, self._rport),
1358
                                  "C")
1359
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1360
          break
1361
    else:
1362
      minor = None
1363

    
1364
    self._SetFromMinor(minor)
1365
    return minor is not None
1366

    
1367

    
1368
  def Open(self, force=False):
1369
    """Make the local state primary.
1370

1371
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1372
    is given. Since this is a pottentialy dangerous operation, the
1373
    force flag should be only given after creation, when it actually
1374
    has to be given.
1375

1376
    """
1377
    if self.minor is None and not self.Attach():
1378
      logger.Error("DRBD cannot attach to a device during open")
1379
      return False
1380
    cmd = ["drbdsetup", self.dev_path, "primary"]
1381
    if force:
1382
      cmd.append("--do-what-I-say")
1383
    result = utils.RunCmd(cmd)
1384
    if result.failed:
1385
      logger.Error("Can't make drbd device primary: %s" % result.output)
1386
      return False
1387
    return True
1388

    
1389

    
1390
  def Close(self):
1391
    """Make the local state secondary.
1392

1393
    This will, of course, fail if the device is in use.
1394

1395
    """
1396
    if self.minor is None and not self.Attach():
1397
      logger.Info("Instance not attached to a device")
1398
      raise errors.BlockDeviceError("Can't find device")
1399
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1400
    if result.failed:
1401
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1402
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1403

    
1404

    
1405
  def SetSyncSpeed(self, kbytes):
1406
    """Set the speed of the DRBD syncer.
1407

1408
    """
1409
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1410
    if self.minor is None:
1411
      logger.Info("Instance not attached to a device")
1412
      return False
1413
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1414
                           kbytes])
1415
    if result.failed:
1416
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1417
    return not result.failed and children_result
1418

    
1419

    
1420
  def GetSyncStatus(self):
1421
    """Returns the sync status of the device.
1422

1423
    Returns:
1424
     (sync_percent, estimated_time)
1425

1426
    If sync_percent is None, it means all is ok
1427
    If estimated_time is None, it means we can't esimate
1428
    the time needed, otherwise it's the time left in seconds
1429

1430
    """
1431
    if self.minor is None and not self.Attach():
1432
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1433
    proc_info = self._MassageProcData(self._GetProcData())
1434
    if self.minor not in proc_info:
1435
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1436
                                    self.minor)
1437
    line = proc_info[self.minor]
1438
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1439
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1440
    if match:
1441
      sync_percent = float(match.group(1))
1442
      hours = int(match.group(2))
1443
      minutes = int(match.group(3))
1444
      seconds = int(match.group(4))
1445
      est_time = hours * 3600 + minutes * 60 + seconds
1446
    else:
1447
      sync_percent = None
1448
      est_time = None
1449
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1450
    if not match:
1451
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1452
                                    self.minor)
1453
    client_state = match.group(1)
1454
    is_degraded = client_state != "Connected"
1455
    return sync_percent, est_time, is_degraded
1456

    
1457

    
1458
  @staticmethod
1459
  def _MassageProcData(data):
1460
    """Transform the output of _GetProdData into a nicer form.
1461

1462
    Returns:
1463
      a dictionary of minor: joined lines from /proc/drbd for that minor
1464

1465
    """
1466
    lmatch = re.compile("^ *([0-9]+):.*$")
1467
    results = {}
1468
    old_minor = old_line = None
1469
    for line in data:
1470
      lresult = lmatch.match(line)
1471
      if lresult is not None:
1472
        if old_minor is not None:
1473
          results[old_minor] = old_line
1474
        old_minor = int(lresult.group(1))
1475
        old_line = line
1476
      else:
1477
        if old_minor is not None:
1478
          old_line += " " + line.strip()
1479
    # add last line
1480
    if old_minor is not None:
1481
      results[old_minor] = old_line
1482
    return results
1483

    
1484

    
1485
  def GetStatus(self):
1486
    """Compute the status of the DRBD device
1487

1488
    Note that DRBD devices don't have the STATUS_EXISTING state.
1489

1490
    """
1491
    if self.minor is None and not self.Attach():
1492
      return self.STATUS_UNKNOWN
1493

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

    
1505
    state = mresult.group(2)
1506
    if state == "Primary":
1507
      result = self.STATUS_ONLINE
1508
    else:
1509
      result = self.STATUS_STANDBY
1510

    
1511
    return result
1512

    
1513

    
1514
  @staticmethod
1515
  def _ZeroDevice(device):
1516
    """Zero a device.
1517

1518
    This writes until we get ENOSPC.
1519

1520
    """
1521
    f = open(device, "w")
1522
    buf = "\0" * 1048576
1523
    try:
1524
      while True:
1525
        f.write(buf)
1526
    except IOError, err:
1527
      if err.errno != errno.ENOSPC:
1528
        raise
1529

    
1530

    
1531
  @classmethod
1532
  def Create(cls, unique_id, children, size):
1533
    """Create a new DRBD device.
1534

1535
    Since DRBD devices are not created per se, just assembled, this
1536
    function just zeroes the meta device.
1537

1538
    """
1539
    if len(children) != 2:
1540
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1541
    meta = children[1]
1542
    meta.Assemble()
1543
    if not meta.Attach():
1544
      raise errors.BlockDeviceError("Can't attach to meta device")
1545
    if not cls._IsValidMeta(meta.dev_path):
1546
      raise errors.BlockDeviceError("Invalid meta device")
1547
    logger.Info("Started zeroing device %s" % meta.dev_path)
1548
    cls._ZeroDevice(meta.dev_path)
1549
    logger.Info("Done zeroing device %s" % meta.dev_path)
1550
    return cls(unique_id, children)
1551

    
1552

    
1553
  def Remove(self):
1554
    """Stub remove for DRBD devices.
1555

1556
    """
1557
    return self.Shutdown()
1558

    
1559

    
1560
DEV_MAP = {
1561
  constants.LD_LV: LogicalVolume,
1562
  constants.LD_MD_R1: MDRaid1,
1563
  constants.LD_DRBD7: DRBDev,
1564
  }
1565

    
1566

    
1567
def FindDevice(dev_type, unique_id, children):
1568
  """Search for an existing, assembled device.
1569

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

1573
  """
1574
  if dev_type not in DEV_MAP:
1575
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1576
  device = DEV_MAP[dev_type](unique_id, children)
1577
  if not device.Attach():
1578
    return None
1579
  return  device
1580

    
1581

    
1582
def AttachOrAssemble(dev_type, unique_id, children):
1583
  """Try to attach or assemble an existing device.
1584

1585
  This will attach to an existing assembled device or will assemble
1586
  the device, as needed, to bring it fully up.
1587

1588
  """
1589
  if dev_type not in DEV_MAP:
1590
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1591
  device = DEV_MAP[dev_type](unique_id, children)
1592
  if not device.Attach():
1593
    device.Assemble()
1594
  if not device.Attach():
1595
    raise errors.BlockDeviceError("Can't find a valid block device for"
1596
                                  " %s/%s/%s" %
1597
                                  (dev_type, unique_id, children))
1598
  return device
1599

    
1600

    
1601
def Create(dev_type, unique_id, children, size):
1602
  """Create a device.
1603

1604
  """
1605
  if dev_type not in DEV_MAP:
1606
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1607
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
1608
  return device