Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 5574047a

History | View | Annotate | Download (72.7 kB)

1
#
2
#
3

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

    
21

    
22
"""Block device abstraction"""
23

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

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

    
34

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

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

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

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

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

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

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

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

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

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

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

    
97
  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
  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
  def Attach(self):
133
    """Find a device which matches our config and attach to it.
134

135
    """
136
    raise NotImplementedError
137

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

141
    """
142
    raise NotImplementedError
143

    
144
  @classmethod
145
  def Create(cls, unique_id, children, size):
146
    """Create the device.
147

148
    If the device cannot be created, it will return None
149
    instead. Error messages go to the logging system.
150

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

155
    """
156
    raise NotImplementedError
157

    
158
  def Remove(self):
159
    """Remove this device.
160

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

165
    """
166
    raise NotImplementedError
167

    
168
  def Rename(self, new_id):
169
    """Rename this device.
170

171
    This may or may not make sense for a given device type.
172

173
    """
174
    raise NotImplementedError
175

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

179
    """
180
    raise NotImplementedError
181

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

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

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

191
    """
192
    raise NotImplementedError
193

    
194
  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
  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
  def GetSyncStatus(self):
217
    """Returns the sync status of the device.
218

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

222
    Returns:
223
     (sync_percent, estimated_time, is_degraded, ldisk)
224

225
    If sync_percent is None, it means the device is not syncing.
226

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

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
    The ldisk parameter represents the degradation of the local
235
    data. This is only valid for some devices, the rest will always
236
    return False (not degraded).
237

238
    """
239
    return None, None, False, False
240

    
241

    
242
  def CombinedSyncStatus(self):
243
    """Calculate the mirror status recursively for our children.
244

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

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

    
266

    
267
  def SetInfo(self, text):
268
    """Update metadata with info text.
269

270
    Only supported for some device types.
271

272
    """
273
    for child in self._children:
274
      child.SetInfo(text)
275

    
276

    
277
  def __repr__(self):
278
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
279
            (self.__class__, self.unique_id, self._children,
280
             self.major, self.minor, self.dev_path))
281

    
282

    
283
class LogicalVolume(BlockDev):
284
  """Logical Volume block device.
285

286
  """
287
  def __init__(self, unique_id, children):
288
    """Attaches to a LV device.
289

290
    The unique_id is a tuple (vg_name, lv_name)
291

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

    
300
  @classmethod
301
  def Create(cls, unique_id, children, size):
302
    """Create a new logical volume.
303

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

    
315
    pvlist = [ pv[1] for pv in pvs_info ]
316
    free_size = sum([ pv[0] for pv in pvs_info ])
317

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

    
329
  @staticmethod
330
  def GetPVInfo(vg_name):
331
    """Get the free space info for PVs in a volume group.
332

333
    Args:
334
      vg_name: the volume group name
335

336
    Returns:
337
      list of (free_space, name) with free_space in mebibytes
338

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

    
358
    return data
359

    
360
  def Remove(self):
361
    """Remove this logical volume.
362

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

    
372
    return not result.failed
373

    
374
  def Rename(self, new_id):
375
    """Rename this logical volume.
376

377
    """
378
    if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
379
      raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
380
    new_vg, new_name = new_id
381
    if new_vg != self._vg_name:
382
      raise errors.ProgrammerError("Can't move a logical volume across"
383
                                   " volume groups (from %s to to %s)" %
384
                                   (self._vg_name, new_vg))
385
    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
386
    if result.failed:
387
      raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
388
                                    result.output)
389
    self._lv_name = new_name
390
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
391

    
392

    
393
  def Attach(self):
394
    """Attach to an existing LV.
395

396
    This method will try to see if an existing and active LV exists
397
    which matches the our name. If so, its major/minor will be
398
    recorded.
399

400
    """
401
    result = utils.RunCmd(["lvdisplay", self.dev_path])
402
    if result.failed:
403
      logger.Error("Can't find LV %s: %s, %s" %
404
                   (self.dev_path, result.fail_reason, result.output))
405
      return False
406
    match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
407
    for line in result.stdout.splitlines():
408
      match_result = match.match(line)
409
      if match_result:
410
        self.major = int(match_result.group(1))
411
        self.minor = int(match_result.group(2))
412
        return True
413
    return False
414

    
415
  def Assemble(self):
416
    """Assemble the device.
417

418
    We alway run `lvchange -ay` on the LV to ensure it's active before
419
    use, as there were cases when xenvg was not active after boot
420
    (also possibly after disk issues).
421

422
    """
423
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
424
    if result.failed:
425
      logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
426
    return not result.failed
427

    
428
  def Shutdown(self):
429
    """Shutdown the device.
430

431
    This is a no-op for the LV device type, as we don't deactivate the
432
    volumes on shutdown.
433

434
    """
435
    return True
436

    
437
  def GetStatus(self):
438
    """Return the status of the device.
439

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

444
    """
445
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
446
    if result.failed:
447
      logger.Error("Can't display lv: %s" % result.fail_reason)
448
      return self.STATUS_UNKNOWN
449
    out = result.stdout.strip()
450
    # format: type/permissions/alloc/fixed_minor/state/open
451
    if len(out) != 6:
452
      return self.STATUS_UNKNOWN
453
    #writable = (out[1] == "w")
454
    active = (out[4] == "a")
455
    online = (out[5] == "o")
456
    if online:
457
      retval = self.STATUS_ONLINE
458
    elif active:
459
      retval = self.STATUS_STANDBY
460
    else:
461
      retval = self.STATUS_EXISTING
462

    
463
    return retval
464

    
465
  def GetSyncStatus(self):
466
    """Returns the sync status of the device.
467

468
    If this device is a mirroring device, this function returns the
469
    status of the mirror.
470

471
    Returns:
472
     (sync_percent, estimated_time, is_degraded, ldisk)
473

474
    For logical volumes, sync_percent and estimated_time are always
475
    None (no recovery in progress, as we don't handle the mirrored LV
476
    case). The is_degraded parameter is the inverse of the ldisk
477
    parameter.
478

479
    For the ldisk parameter, we check if the logical volume has the
480
    'virtual' type, which means it's not backed by existing storage
481
    anymore (read from it return I/O error). This happens after a
482
    physical disk failure and subsequent 'vgreduce --removemissing' on
483
    the volume group.
484

485
    """
486
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
487
    if result.failed:
488
      logger.Error("Can't display lv: %s" % result.fail_reason)
489
      return None, None, True, True
490
    out = result.stdout.strip()
491
    # format: type/permissions/alloc/fixed_minor/state/open
492
    if len(out) != 6:
493
      logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
494
      return None, None, True, True
495
    ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
496
                          # backing storage
497
    return None, None, ldisk, ldisk
498

    
499
  def Open(self, force=False):
500
    """Make the device ready for I/O.
501

502
    This is a no-op for the LV device type.
503

504
    """
505
    return True
506

    
507
  def Close(self):
508
    """Notifies that the device will no longer be used for I/O.
509

510
    This is a no-op for the LV device type.
511

512
    """
513
    return True
514

    
515
  def Snapshot(self, size):
516
    """Create a snapshot copy of an lvm block device.
517

518
    """
519
    snap_name = self._lv_name + ".snap"
520

    
521
    # remove existing snapshot if found
522
    snap = LogicalVolume((self._vg_name, snap_name), None)
523
    snap.Remove()
524

    
525
    pvs_info = self.GetPVInfo(self._vg_name)
526
    if not pvs_info:
527
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
528
                                    self._vg_name)
529
    pvs_info.sort()
530
    pvs_info.reverse()
531
    free_size, pv_name = pvs_info[0]
532
    if free_size < size:
533
      raise errors.BlockDeviceError("Not enough free space: required %s,"
534
                                    " available %s" % (size, free_size))
535

    
536
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
537
                           "-n%s" % snap_name, self.dev_path])
538
    if result.failed:
539
      raise errors.BlockDeviceError("command: %s error: %s" %
540
                                    (result.cmd, result.fail_reason))
541

    
542
    return snap_name
543

    
544
  def SetInfo(self, text):
545
    """Update metadata with info text.
546

547
    """
548
    BlockDev.SetInfo(self, text)
549

    
550
    # Replace invalid characters
551
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
552
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
553

    
554
    # Only up to 128 characters are allowed
555
    text = text[:128]
556

    
557
    result = utils.RunCmd(["lvchange", "--addtag", text,
558
                           self.dev_path])
559
    if result.failed:
560
      raise errors.BlockDeviceError("Command: %s error: %s" %
561
                                    (result.cmd, result.fail_reason))
562

    
563

    
564
class MDRaid1(BlockDev):
565
  """raid1 device implemented via md.
566

567
  """
568
  def __init__(self, unique_id, children):
569
    super(MDRaid1, self).__init__(unique_id, children)
570
    self.major = 9
571
    self.Attach()
572

    
573
  def Attach(self):
574
    """Find an array which matches our config and attach to it.
575

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

578
    """
579
    minor = self._FindMDByUUID(self.unique_id)
580
    if minor is not None:
581
      self._SetFromMinor(minor)
582
    else:
583
      self.minor = None
584
      self.dev_path = None
585

    
586
    return (minor is not None)
587

    
588
  @staticmethod
589
  def _GetUsedDevs():
590
    """Compute the list of in-use MD devices.
591

592
    It doesn't matter if the used device have other raid level, just
593
    that they are in use.
594

595
    """
596
    mdstat = open("/proc/mdstat", "r")
597
    data = mdstat.readlines()
598
    mdstat.close()
599

    
600
    used_md = {}
601
    valid_line = re.compile("^md([0-9]+) : .*$")
602
    for line in data:
603
      match = valid_line.match(line)
604
      if match:
605
        md_no = int(match.group(1))
606
        used_md[md_no] = line
607

    
608
    return used_md
609

    
610
  @staticmethod
611
  def _GetDevInfo(minor):
612
    """Get info about a MD device.
613

614
    Currently only uuid is returned.
615

616
    """
617
    result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
618
    if result.failed:
619
      logger.Error("Can't display md: %s" % result.fail_reason)
620
      return None
621
    retval = {}
622
    for line in result.stdout.splitlines():
623
      line = line.strip()
624
      kv = line.split(" : ", 1)
625
      if kv:
626
        if kv[0] == "UUID":
627
          retval["uuid"] = kv[1].split()[0]
628
        elif kv[0] == "State":
629
          retval["state"] = kv[1].split(", ")
630
    return retval
631

    
632
  @staticmethod
633
  def _FindUnusedMinor():
634
    """Compute an unused MD minor.
635

636
    This code assumes that there are 256 minors only.
637

638
    """
639
    used_md = MDRaid1._GetUsedDevs()
640
    i = 0
641
    while i < 256:
642
      if i not in used_md:
643
        break
644
      i += 1
645
    if i == 256:
646
      logger.Error("Critical: Out of md minor numbers.")
647
      raise errors.BlockDeviceError("Can't find a free MD minor")
648
    return i
649

    
650
  @classmethod
651
  def _FindMDByUUID(cls, uuid):
652
    """Find the minor of an MD array with a given UUID.
653

654
    """
655
    md_list = cls._GetUsedDevs()
656
    for minor in md_list:
657
      info = cls._GetDevInfo(minor)
658
      if info and info["uuid"] == uuid:
659
        return minor
660
    return None
661

    
662
  @staticmethod
663
  def _ZeroSuperblock(dev_path):
664
    """Zero the possible locations for an MD superblock.
665

666
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
667
    fails in versions 2.x with the same error code as non-writable
668
    device.
669

670
    The superblocks are located at (negative values are relative to
671
    the end of the block device):
672
      - -128k to end for version 0.90 superblock
673
      - -8k to -12k for version 1.0 superblock (included in the above)
674
      - 0k to 4k for version 1.1 superblock
675
      - 4k to 8k for version 1.2 superblock
676

677
    To cover all situations, the zero-ing will be:
678
      - 0k to 128k
679
      - -128k to end
680

681
    As such, the minimum device size must be 128k, otherwise we'll get
682
    I/O errors.
683

684
    Note that this function depends on the fact that one can open,
685
    read and write block devices normally.
686

687
    """
688
    overwrite_size = 128 * 1024
689
    empty_buf = '\0' * overwrite_size
690
    fd = open(dev_path, "r+")
691
    try:
692
      fd.seek(0, 0)
693
      p1 = fd.tell()
694
      fd.write(empty_buf)
695
      p2 = fd.tell()
696
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
697
      fd.seek(-overwrite_size, 2)
698
      p1 = fd.tell()
699
      fd.write(empty_buf)
700
      p2 = fd.tell()
701
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
702
    finally:
703
      fd.close()
704

    
705
  @classmethod
706
  def Create(cls, unique_id, children, size):
707
    """Create a new MD raid1 array.
708

709
    """
710
    if not isinstance(children, (tuple, list)):
711
      raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
712
                       str(children))
713
    for i in children:
714
      if not isinstance(i, BlockDev):
715
        raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
716
    for i in children:
717
      try:
718
        cls._ZeroSuperblock(i.dev_path)
719
      except EnvironmentError, err:
720
        logger.Error("Can't zero superblock for %s: %s" %
721
                     (i.dev_path, str(err)))
722
        return None
723
    minor = cls._FindUnusedMinor()
724
    result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
725
                           "--auto=yes", "--force", "-l1",
726
                           "-n%d" % len(children)] +
727
                          [dev.dev_path for dev in children])
728

    
729
    if result.failed:
730
      logger.Error("Can't create md: %s: %s" % (result.fail_reason,
731
                                                result.output))
732
      return None
733
    info = cls._GetDevInfo(minor)
734
    if not info or not "uuid" in info:
735
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
736
      return None
737
    return MDRaid1(info["uuid"], children)
738

    
739
  def Remove(self):
740
    """Stub remove function for MD RAID 1 arrays.
741

742
    We don't remove the superblock right now. Mark a to do.
743

744
    """
745
    #TODO: maybe zero superblock on child devices?
746
    return self.Shutdown()
747

    
748
  def Rename(self, new_id):
749
    """Rename a device.
750

751
    This is not supported for md raid1 devices.
752

753
    """
754
    raise errors.ProgrammerError("Can't rename a md raid1 device")
755

    
756
  def AddChildren(self, devices):
757
    """Add new member(s) to the md raid1.
758

759
    """
760
    if self.minor is None and not self.Attach():
761
      raise errors.BlockDeviceError("Can't attach to device")
762

    
763
    args = ["mdadm", "-a", self.dev_path]
764
    for dev in devices:
765
      if dev.dev_path is None:
766
        raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
767
      dev.Open()
768
      args.append(dev.dev_path)
769
    result = utils.RunCmd(args)
770
    if result.failed:
771
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
772
                                    result.output)
773
    new_len = len(self._children) + len(devices)
774
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
775
    if result.failed:
776
      raise errors.BlockDeviceError("Can't grow md array: %s" %
777
                                    result.output)
778
    self._children.extend(devices)
779

    
780
  def RemoveChildren(self, devices):
781
    """Remove member(s) from the md raid1.
782

783
    """
784
    if self.minor is None and not self.Attach():
785
      raise errors.BlockDeviceError("Can't attach to device")
786
    new_len = len(self._children) - len(devices)
787
    if new_len < 1:
788
      raise errors.BlockDeviceError("Can't reduce to less than one child")
789
    args = ["mdadm", "-f", self.dev_path]
790
    orig_devs = []
791
    for dev in devices:
792
      args.append(dev)
793
      for c in self._children:
794
        if c.dev_path == dev:
795
          orig_devs.append(c)
796
          break
797
      else:
798
        raise errors.BlockDeviceError("Can't find device '%s' for removal" %
799
                                      dev)
800
    result = utils.RunCmd(args)
801
    if result.failed:
802
      raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
803
                                    result.output)
804

    
805
    # it seems here we need a short delay for MD to update its
806
    # superblocks
807
    time.sleep(0.5)
808
    args[1] = "-r"
809
    result = utils.RunCmd(args)
810
    if result.failed:
811
      raise errors.BlockDeviceError("Failed to remove device(s) from array:"
812
                                    " %s" % result.output)
813
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
814
                           "-n", new_len])
815
    if result.failed:
816
      raise errors.BlockDeviceError("Can't shrink md array: %s" %
817
                                    result.output)
818
    for dev in orig_devs:
819
      self._children.remove(dev)
820

    
821
  def GetStatus(self):
822
    """Return the status of the device.
823

824
    """
825
    self.Attach()
826
    if self.minor is None:
827
      retval = self.STATUS_UNKNOWN
828
    else:
829
      retval = self.STATUS_ONLINE
830
    return retval
831

    
832
  def _SetFromMinor(self, minor):
833
    """Set our parameters based on the given minor.
834

835
    This sets our minor variable and our dev_path.
836

837
    """
838
    self.minor = minor
839
    self.dev_path = "/dev/md%d" % minor
840

    
841
  def Assemble(self):
842
    """Assemble the MD device.
843

844
    At this point we should have:
845
      - list of children devices
846
      - uuid
847

848
    """
849
    result = super(MDRaid1, self).Assemble()
850
    if not result:
851
      return result
852
    md_list = self._GetUsedDevs()
853
    for minor in md_list:
854
      info = self._GetDevInfo(minor)
855
      if info and info["uuid"] == self.unique_id:
856
        self._SetFromMinor(minor)
857
        logger.Info("MD array %s already started" % str(self))
858
        return True
859
    free_minor = self._FindUnusedMinor()
860
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
861
                           self.unique_id, "/dev/md%d" % free_minor] +
862
                          [bdev.dev_path for bdev in self._children])
863
    if result.failed:
864
      logger.Error("Can't assemble MD array: %s: %s" %
865
                   (result.fail_reason, result.output))
866
      self.minor = None
867
    else:
868
      self.minor = free_minor
869
    return not result.failed
870

    
871
  def Shutdown(self):
872
    """Tear down the MD array.
873

874
    This does a 'mdadm --stop' so after this command, the array is no
875
    longer available.
876

877
    """
878
    if self.minor is None and not self.Attach():
879
      logger.Info("MD object not attached to a device")
880
      return True
881

    
882
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
883
    if result.failed:
884
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
885
      return False
886
    self.minor = None
887
    self.dev_path = None
888
    return True
889

    
890
  def SetSyncSpeed(self, kbytes):
891
    """Set the maximum sync speed for the MD array.
892

893
    """
894
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
895
    if self.minor is None:
896
      logger.Error("MD array not attached to a device")
897
      return False
898
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
899
    try:
900
      f.write("%d" % kbytes)
901
    finally:
902
      f.close()
903
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
904
    try:
905
      f.write("%d" % (kbytes/2))
906
    finally:
907
      f.close()
908
    return result
909

    
910
  def GetSyncStatus(self):
911
    """Returns the sync status of the device.
912

913
    Returns:
914
     (sync_percent, estimated_time, is_degraded, ldisk)
915

916
    If sync_percent is None, it means all is ok
917
    If estimated_time is None, it means we can't esimate
918
    the time needed, otherwise it's the time left in seconds.
919

920
    The ldisk parameter is always true for MD devices.
921

922
    """
923
    if self.minor is None and not self.Attach():
924
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
925
    dev_info = self._GetDevInfo(self.minor)
926
    is_clean = ("state" in dev_info and
927
                len(dev_info["state"]) == 1 and
928
                dev_info["state"][0] in ("clean", "active"))
929
    sys_path = "/sys/block/md%s/md/" % self.minor
930
    f = file(sys_path + "sync_action")
931
    sync_status = f.readline().strip()
932
    f.close()
933
    if sync_status == "idle":
934
      return None, None, not is_clean, False
935
    f = file(sys_path + "sync_completed")
936
    sync_completed = f.readline().strip().split(" / ")
937
    f.close()
938
    if len(sync_completed) != 2:
939
      return 0, None, not is_clean, False
940
    sync_done, sync_total = [float(i) for i in sync_completed]
941
    sync_percent = 100.0*sync_done/sync_total
942
    f = file(sys_path + "sync_speed")
943
    sync_speed_k = int(f.readline().strip())
944
    if sync_speed_k == 0:
945
      time_est = None
946
    else:
947
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
948
    return sync_percent, time_est, not is_clean, False
949

    
950
  def Open(self, force=False):
951
    """Make the device ready for I/O.
952

953
    This is a no-op for the MDRaid1 device type, although we could use
954
    the 2.6.18's new array_state thing.
955

956
    """
957
    return True
958

    
959
  def Close(self):
960
    """Notifies that the device will no longer be used for I/O.
961

962
    This is a no-op for the MDRaid1 device type, but see comment for
963
    `Open()`.
964

965
    """
966
    return True
967

    
968

    
969
class BaseDRBD(BlockDev):
970
  """Base DRBD class.
971

972
  This class contains a few bits of common functionality between the
973
  0.7 and 8.x versions of DRBD.
974

975
  """
976
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
977
                           r" \(api:(\d+)/proto:(\d+)\)")
978
  _DRBD_MAJOR = 147
979
  _ST_UNCONFIGURED = "Unconfigured"
980
  _ST_WFCONNECTION = "WFConnection"
981
  _ST_CONNECTED = "Connected"
982

    
983
  @staticmethod
984
  def _GetProcData():
985
    """Return data from /proc/drbd.
986

987
    """
988
    stat = open("/proc/drbd", "r")
989
    try:
990
      data = stat.read().splitlines()
991
    finally:
992
      stat.close()
993
    if not data:
994
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
995
    return data
996

    
997
  @staticmethod
998
  def _MassageProcData(data):
999
    """Transform the output of _GetProdData into a nicer form.
1000

1001
    Returns:
1002
      a dictionary of minor: joined lines from /proc/drbd for that minor
1003

1004
    """
1005
    lmatch = re.compile("^ *([0-9]+):.*$")
1006
    results = {}
1007
    old_minor = old_line = None
1008
    for line in data:
1009
      lresult = lmatch.match(line)
1010
      if lresult is not None:
1011
        if old_minor is not None:
1012
          results[old_minor] = old_line
1013
        old_minor = int(lresult.group(1))
1014
        old_line = line
1015
      else:
1016
        if old_minor is not None:
1017
          old_line += " " + line.strip()
1018
    # add last line
1019
    if old_minor is not None:
1020
      results[old_minor] = old_line
1021
    return results
1022

    
1023
  @classmethod
1024
  def _GetVersion(cls):
1025
    """Return the DRBD version.
1026

1027
    This will return a list [k_major, k_minor, k_point, api, proto].
1028

1029
    """
1030
    proc_data = cls._GetProcData()
1031
    first_line = proc_data[0].strip()
1032
    version = cls._VERSION_RE.match(first_line)
1033
    if not version:
1034
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1035
                                    first_line)
1036
    return [int(val) for val in version.groups()]
1037

    
1038
  @staticmethod
1039
  def _DevPath(minor):
1040
    """Return the path to a drbd device for a given minor.
1041

1042
    """
1043
    return "/dev/drbd%d" % minor
1044

    
1045
  @classmethod
1046
  def _GetUsedDevs(cls):
1047
    """Compute the list of used DRBD devices.
1048

1049
    """
1050
    data = cls._GetProcData()
1051

    
1052
    used_devs = {}
1053
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1054
    for line in data:
1055
      match = valid_line.match(line)
1056
      if not match:
1057
        continue
1058
      minor = int(match.group(1))
1059
      state = match.group(2)
1060
      if state == cls._ST_UNCONFIGURED:
1061
        continue
1062
      used_devs[minor] = state, line
1063

    
1064
    return used_devs
1065

    
1066
  def _SetFromMinor(self, minor):
1067
    """Set our parameters based on the given minor.
1068

1069
    This sets our minor variable and our dev_path.
1070

1071
    """
1072
    if minor is None:
1073
      self.minor = self.dev_path = None
1074
    else:
1075
      self.minor = minor
1076
      self.dev_path = self._DevPath(minor)
1077

    
1078
  @staticmethod
1079
  def _CheckMetaSize(meta_device):
1080
    """Check if the given meta device looks like a valid one.
1081

1082
    This currently only check the size, which must be around
1083
    128MiB.
1084

1085
    """
1086
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1087
    if result.failed:
1088
      logger.Error("Failed to get device size: %s" % result.fail_reason)
1089
      return False
1090
    try:
1091
      sectors = int(result.stdout)
1092
    except ValueError:
1093
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1094
      return False
1095
    bytes = sectors * 512
1096
    if bytes < 128 * 1024 * 1024: # less than 128MiB
1097
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1098
      return False
1099
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1100
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1101
      return False
1102
    return True
1103

    
1104
  def Rename(self, new_id):
1105
    """Rename a device.
1106

1107
    This is not supported for drbd devices.
1108

1109
    """
1110
    raise errors.ProgrammerError("Can't rename a drbd device")
1111

    
1112

    
1113
class DRBDev(BaseDRBD):
1114
  """DRBD block device.
1115

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

1120
  The unique_id for the drbd device is the (local_ip, local_port,
1121
  remote_ip, remote_port) tuple, and it must have two children: the
1122
  data device and the meta_device. The meta device is checked for
1123
  valid size and is zeroed on create.
1124

1125
  """
1126
  def __init__(self, unique_id, children):
1127
    super(DRBDev, self).__init__(unique_id, children)
1128
    self.major = self._DRBD_MAJOR
1129
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1130
    if kmaj != 0 and kmin != 7:
1131
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1132
                                    " requested ganeti usage: kernel is"
1133
                                    " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1134

    
1135
    if len(children) != 2:
1136
      raise ValueError("Invalid configuration data %s" % str(children))
1137
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1138
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1139
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1140
    self.Attach()
1141

    
1142
  @classmethod
1143
  def _FindUnusedMinor(cls):
1144
    """Find an unused DRBD device.
1145

1146
    """
1147
    data = cls._GetProcData()
1148

    
1149
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1150
    for line in data:
1151
      match = valid_line.match(line)
1152
      if match:
1153
        return int(match.group(1))
1154
    logger.Error("Error: no free drbd minors!")
1155
    raise errors.BlockDeviceError("Can't find a free DRBD minor")
1156

    
1157
  @classmethod
1158
  def _GetDevInfo(cls, minor):
1159
    """Get details about a given DRBD minor.
1160

1161
    This return, if available, the local backing device in (major,
1162
    minor) formant and the local and remote (ip, port) information.
1163

1164
    """
1165
    data = {}
1166
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1167
    if result.failed:
1168
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1169
      return data
1170
    out = result.stdout
1171
    if out == "Not configured\n":
1172
      return data
1173
    for line in out.splitlines():
1174
      if "local_dev" not in data:
1175
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1176
        if match:
1177
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1178
          continue
1179
      if "meta_dev" not in data:
1180
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1181
        if match:
1182
          if match.group(2) is not None and match.group(3) is not None:
1183
            # matched on the major/minor
1184
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1185
          else:
1186
            # matched on the "internal" string
1187
            data["meta_dev"] = match.group(1)
1188
            # in this case, no meta_index is in the output
1189
            data["meta_index"] = -1
1190
          continue
1191
      if "meta_index" not in data:
1192
        match = re.match("^Meta index: ([0-9]+).*$", line)
1193
        if match:
1194
          data["meta_index"] = int(match.group(1))
1195
          continue
1196
      if "local_addr" not in data:
1197
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1198
        if match:
1199
          data["local_addr"] = (match.group(1), int(match.group(2)))
1200
          continue
1201
      if "remote_addr" not in data:
1202
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1203
        if match:
1204
          data["remote_addr"] = (match.group(1), int(match.group(2)))
1205
          continue
1206
    return data
1207

    
1208
  def _MatchesLocal(self, info):
1209
    """Test if our local config matches with an existing device.
1210

1211
    The parameter should be as returned from `_GetDevInfo()`. This
1212
    method tests if our local backing device is the same as the one in
1213
    the info parameter, in effect testing if we look like the given
1214
    device.
1215

1216
    """
1217
    if not ("local_dev" in info and "meta_dev" in info and
1218
            "meta_index" in info):
1219
      return False
1220

    
1221
    backend = self._children[0]
1222
    if backend is not None:
1223
      retval = (info["local_dev"] == (backend.major, backend.minor))
1224
    else:
1225
      retval = (info["local_dev"] == (0, 0))
1226
    meta = self._children[1]
1227
    if meta is not None:
1228
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1229
      retval = retval and (info["meta_index"] == 0)
1230
    else:
1231
      retval = retval and (info["meta_dev"] == "internal" and
1232
                           info["meta_index"] == -1)
1233
    return retval
1234

    
1235
  def _MatchesNet(self, info):
1236
    """Test if our network config matches with an existing device.
1237

1238
    The parameter should be as returned from `_GetDevInfo()`. This
1239
    method tests if our network configuration is the same as the one
1240
    in the info parameter, in effect testing if we look like the given
1241
    device.
1242

1243
    """
1244
    if (((self._lhost is None and not ("local_addr" in info)) and
1245
         (self._rhost is None and not ("remote_addr" in info)))):
1246
      return True
1247

    
1248
    if self._lhost is None:
1249
      return False
1250

    
1251
    if not ("local_addr" in info and
1252
            "remote_addr" in info):
1253
      return False
1254

    
1255
    retval = (info["local_addr"] == (self._lhost, self._lport))
1256
    retval = (retval and
1257
              info["remote_addr"] == (self._rhost, self._rport))
1258
    return retval
1259

    
1260
  @classmethod
1261
  def _AssembleLocal(cls, minor, backend, meta):
1262
    """Configure the local part of a DRBD device.
1263

1264
    This is the first thing that must be done on an unconfigured DRBD
1265
    device. And it must be done only once.
1266

1267
    """
1268
    if not cls._CheckMetaSize(meta):
1269
      return False
1270
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1271
                           backend, meta, "0", "-e", "detach"])
1272
    if result.failed:
1273
      logger.Error("Can't attach local disk: %s" % result.output)
1274
    return not result.failed
1275

    
1276
  @classmethod
1277
  def _ShutdownLocal(cls, minor):
1278
    """Detach from the local device.
1279

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

1283
    """
1284
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1285
    if result.failed:
1286
      logger.Error("Can't detach local device: %s" % result.output)
1287
    return not result.failed
1288

    
1289
  @staticmethod
1290
  def _ShutdownAll(minor):
1291
    """Deactivate the device.
1292

1293
    This will, of course, fail if the device is in use.
1294

1295
    """
1296
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1297
    if result.failed:
1298
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1299
    return not result.failed
1300

    
1301
  @classmethod
1302
  def _AssembleNet(cls, minor, net_info, protocol):
1303
    """Configure the network part of the device.
1304

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

1312
    """
1313
    lhost, lport, rhost, rport = net_info
1314
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1315
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1316
                           protocol])
1317
    if result.failed:
1318
      logger.Error("Can't setup network for dbrd device: %s" %
1319
                   result.fail_reason)
1320
      return False
1321

    
1322
    timeout = time.time() + 10
1323
    ok = False
1324
    while time.time() < timeout:
1325
      info = cls._GetDevInfo(minor)
1326
      if not "local_addr" in info or not "remote_addr" in info:
1327
        time.sleep(1)
1328
        continue
1329
      if (info["local_addr"] != (lhost, lport) or
1330
          info["remote_addr"] != (rhost, rport)):
1331
        time.sleep(1)
1332
        continue
1333
      ok = True
1334
      break
1335
    if not ok:
1336
      logger.Error("Timeout while configuring network")
1337
      return False
1338
    return True
1339

    
1340
  @classmethod
1341
  def _ShutdownNet(cls, minor):
1342
    """Disconnect from the remote peer.
1343

1344
    This fails if we don't have a local device.
1345

1346
    """
1347
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1348
    if result.failed:
1349
      logger.Error("Can't shutdown network: %s" % result.output)
1350
    return not result.failed
1351

    
1352
  def Assemble(self):
1353
    """Assemble the drbd.
1354

1355
    Method:
1356
      - if we have a local backing device, we bind to it by:
1357
        - checking the list of used drbd devices
1358
        - check if the local minor use of any of them is our own device
1359
        - if yes, abort?
1360
        - if not, bind
1361
      - if we have a local/remote net info:
1362
        - redo the local backing device step for the remote device
1363
        - check if any drbd device is using the local port,
1364
          if yes abort
1365
        - check if any remote drbd device is using the remote
1366
          port, if yes abort (for now)
1367
        - bind our net port
1368
        - bind the remote net port
1369

1370
    """
1371
    self.Attach()
1372
    if self.minor is not None:
1373
      logger.Info("Already assembled")
1374
      return True
1375

    
1376
    result = super(DRBDev, self).Assemble()
1377
    if not result:
1378
      return result
1379

    
1380
    minor = self._FindUnusedMinor()
1381
    need_localdev_teardown = False
1382
    if self._children[0]:
1383
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1384
                                   self._children[1].dev_path)
1385
      if not result:
1386
        return False
1387
      need_localdev_teardown = True
1388
    if self._lhost and self._lport and self._rhost and self._rport:
1389
      result = self._AssembleNet(minor,
1390
                                 (self._lhost, self._lport,
1391
                                  self._rhost, self._rport),
1392
                                 "C")
1393
      if not result:
1394
        if need_localdev_teardown:
1395
          # we will ignore failures from this
1396
          logger.Error("net setup failed, tearing down local device")
1397
          self._ShutdownAll(minor)
1398
        return False
1399
    self._SetFromMinor(minor)
1400
    return True
1401

    
1402
  def Shutdown(self):
1403
    """Shutdown the DRBD device.
1404

1405
    """
1406
    if self.minor is None and not self.Attach():
1407
      logger.Info("DRBD device not attached to a device during Shutdown")
1408
      return True
1409
    if not self._ShutdownAll(self.minor):
1410
      return False
1411
    self.minor = None
1412
    self.dev_path = None
1413
    return True
1414

    
1415
  def Attach(self):
1416
    """Find a DRBD device which matches our config and attach to it.
1417

1418
    In case of partially attached (local device matches but no network
1419
    setup), we perform the network attach. If successful, we re-test
1420
    the attach if can return success.
1421

1422
    """
1423
    for minor in self._GetUsedDevs():
1424
      info = self._GetDevInfo(minor)
1425
      match_l = self._MatchesLocal(info)
1426
      match_r = self._MatchesNet(info)
1427
      if match_l and match_r:
1428
        break
1429
      if match_l and not match_r and "local_addr" not in info:
1430
        res_r = self._AssembleNet(minor,
1431
                                  (self._lhost, self._lport,
1432
                                   self._rhost, self._rport),
1433
                                  "C")
1434
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1435
          break
1436
    else:
1437
      minor = None
1438

    
1439
    self._SetFromMinor(minor)
1440
    return minor is not None
1441

    
1442
  def Open(self, force=False):
1443
    """Make the local state primary.
1444

1445
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1446
    is given. Since this is a pottentialy dangerous operation, the
1447
    force flag should be only given after creation, when it actually
1448
    has to be given.
1449

1450
    """
1451
    if self.minor is None and not self.Attach():
1452
      logger.Error("DRBD cannot attach to a device during open")
1453
      return False
1454
    cmd = ["drbdsetup", self.dev_path, "primary"]
1455
    if force:
1456
      cmd.append("--do-what-I-say")
1457
    result = utils.RunCmd(cmd)
1458
    if result.failed:
1459
      logger.Error("Can't make drbd device primary: %s" % result.output)
1460
      return False
1461
    return True
1462

    
1463
  def Close(self):
1464
    """Make the local state secondary.
1465

1466
    This will, of course, fail if the device is in use.
1467

1468
    """
1469
    if self.minor is None and not self.Attach():
1470
      logger.Info("Instance not attached to a device")
1471
      raise errors.BlockDeviceError("Can't find device")
1472
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1473
    if result.failed:
1474
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1475
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1476

    
1477
  def SetSyncSpeed(self, kbytes):
1478
    """Set the speed of the DRBD syncer.
1479

1480
    """
1481
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1482
    if self.minor is None:
1483
      logger.Info("Instance not attached to a device")
1484
      return False
1485
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1486
                           kbytes])
1487
    if result.failed:
1488
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1489
    return not result.failed and children_result
1490

    
1491
  def GetSyncStatus(self):
1492
    """Returns the sync status of the device.
1493

1494
    Returns:
1495
     (sync_percent, estimated_time, is_degraded, ldisk)
1496

1497
    If sync_percent is None, it means all is ok
1498
    If estimated_time is None, it means we can't esimate
1499
    the time needed, otherwise it's the time left in seconds.
1500

1501
    The ldisk parameter will be returned as True, since the DRBD7
1502
    devices have not been converted.
1503

1504
    """
1505
    if self.minor is None and not self.Attach():
1506
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1507
    proc_info = self._MassageProcData(self._GetProcData())
1508
    if self.minor not in proc_info:
1509
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1510
                                    self.minor)
1511
    line = proc_info[self.minor]
1512
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1513
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1514
    if match:
1515
      sync_percent = float(match.group(1))
1516
      hours = int(match.group(2))
1517
      minutes = int(match.group(3))
1518
      seconds = int(match.group(4))
1519
      est_time = hours * 3600 + minutes * 60 + seconds
1520
    else:
1521
      sync_percent = None
1522
      est_time = None
1523
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1524
    if not match:
1525
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1526
                                    self.minor)
1527
    client_state = match.group(1)
1528
    is_degraded = client_state != "Connected"
1529
    return sync_percent, est_time, is_degraded, False
1530

    
1531
  def GetStatus(self):
1532
    """Compute the status of the DRBD device
1533

1534
    Note that DRBD devices don't have the STATUS_EXISTING state.
1535

1536
    """
1537
    if self.minor is None and not self.Attach():
1538
      return self.STATUS_UNKNOWN
1539

    
1540
    data = self._GetProcData()
1541
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1542
                       self.minor)
1543
    for line in data:
1544
      mresult = match.match(line)
1545
      if mresult:
1546
        break
1547
    else:
1548
      logger.Error("Can't find myself!")
1549
      return self.STATUS_UNKNOWN
1550

    
1551
    state = mresult.group(2)
1552
    if state == "Primary":
1553
      result = self.STATUS_ONLINE
1554
    else:
1555
      result = self.STATUS_STANDBY
1556

    
1557
    return result
1558

    
1559
  @staticmethod
1560
  def _ZeroDevice(device):
1561
    """Zero a device.
1562

1563
    This writes until we get ENOSPC.
1564

1565
    """
1566
    f = open(device, "w")
1567
    buf = "\0" * 1048576
1568
    try:
1569
      while True:
1570
        f.write(buf)
1571
    except IOError, err:
1572
      if err.errno != errno.ENOSPC:
1573
        raise
1574

    
1575
  @classmethod
1576
  def Create(cls, unique_id, children, size):
1577
    """Create a new DRBD device.
1578

1579
    Since DRBD devices are not created per se, just assembled, this
1580
    function just zeroes the meta device.
1581

1582
    """
1583
    if len(children) != 2:
1584
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1585
    meta = children[1]
1586
    meta.Assemble()
1587
    if not meta.Attach():
1588
      raise errors.BlockDeviceError("Can't attach to meta device")
1589
    if not cls._CheckMetaSize(meta.dev_path):
1590
      raise errors.BlockDeviceError("Invalid meta device")
1591
    logger.Info("Started zeroing device %s" % meta.dev_path)
1592
    cls._ZeroDevice(meta.dev_path)
1593
    logger.Info("Done zeroing device %s" % meta.dev_path)
1594
    return cls(unique_id, children)
1595

    
1596
  def Remove(self):
1597
    """Stub remove for DRBD devices.
1598

1599
    """
1600
    return self.Shutdown()
1601

    
1602

    
1603
class DRBD8(BaseDRBD):
1604
  """DRBD v8.x block device.
1605

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

1610
  The unique_id for the drbd device is the (local_ip, local_port,
1611
  remote_ip, remote_port) tuple, and it must have two children: the
1612
  data device and the meta_device. The meta device is checked for
1613
  valid size and is zeroed on create.
1614

1615
  """
1616
  _MAX_MINORS = 255
1617
  _PARSE_SHOW = None
1618

    
1619
  def __init__(self, unique_id, children):
1620
    if children and children.count(None) > 0:
1621
      children = []
1622
    super(DRBD8, self).__init__(unique_id, children)
1623
    self.major = self._DRBD_MAJOR
1624
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1625
    if kmaj != 8:
1626
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1627
                                    " requested ganeti usage: kernel is"
1628
                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1629

    
1630
    if len(children) not in (0, 2):
1631
      raise ValueError("Invalid configuration data %s" % str(children))
1632
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1633
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1634
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1635
    self.Attach()
1636

    
1637
  @classmethod
1638
  def _InitMeta(cls, minor, dev_path):
1639
    """Initialize a meta device.
1640

1641
    This will not work if the given minor is in use.
1642

1643
    """
1644
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1645
                           "v08", dev_path, "0", "create-md"])
1646
    if result.failed:
1647
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1648
                                    result.output)
1649

    
1650
  @classmethod
1651
  def _FindUnusedMinor(cls):
1652
    """Find an unused DRBD device.
1653

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

1657
    """
1658
    data = cls._GetProcData()
1659

    
1660
    unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1661
    used_line = re.compile("^ *([0-9]+): cs:")
1662
    highest = None
1663
    for line in data:
1664
      match = unused_line.match(line)
1665
      if match:
1666
        return int(match.group(1))
1667
      match = used_line.match(line)
1668
      if match:
1669
        minor = int(match.group(1))
1670
        highest = max(highest, minor)
1671
    if highest is None: # there are no minors in use at all
1672
      return 0
1673
    if highest >= cls._MAX_MINORS:
1674
      logger.Error("Error: no free drbd minors!")
1675
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1676
    return highest + 1
1677

    
1678
  @classmethod
1679
  def _IsValidMeta(cls, meta_device):
1680
    """Check if the given meta device looks like a valid one.
1681

1682
    """
1683
    minor = cls._FindUnusedMinor()
1684
    minor_path = cls._DevPath(minor)
1685
    result = utils.RunCmd(["drbdmeta", minor_path,
1686
                           "v08", meta_device, "0",
1687
                           "dstate"])
1688
    if result.failed:
1689
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1690
      return False
1691
    return True
1692

    
1693
  @classmethod
1694
  def _GetShowParser(cls):
1695
    """Return a parser for `drbd show` output.
1696

1697
    This will either create or return an already-create parser for the
1698
    output of the command `drbd show`.
1699

1700
    """
1701
    if cls._PARSE_SHOW is not None:
1702
      return cls._PARSE_SHOW
1703

    
1704
    # pyparsing setup
1705
    lbrace = pyp.Literal("{").suppress()
1706
    rbrace = pyp.Literal("}").suppress()
1707
    semi = pyp.Literal(";").suppress()
1708
    # this also converts the value to an int
1709
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1710

    
1711
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1712
    defa = pyp.Literal("_is_default").suppress()
1713
    dbl_quote = pyp.Literal('"').suppress()
1714

    
1715
    keyword = pyp.Word(pyp.alphanums + '-')
1716

    
1717
    # value types
1718
    value = pyp.Word(pyp.alphanums + '_-/.:')
1719
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1720
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1721
                 number)
1722
    # meta device, extended syntax
1723
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1724
                  number + pyp.Word(']').suppress())
1725

    
1726
    # a statement
1727
    stmt = (~rbrace + keyword + ~lbrace +
1728
            (addr_port ^ value ^ quoted ^ meta_value) +
1729
            pyp.Optional(defa) + semi +
1730
            pyp.Optional(pyp.restOfLine).suppress())
1731

    
1732
    # an entire section
1733
    section_name = pyp.Word(pyp.alphas + '_')
1734
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1735

    
1736
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1737
    bnf.ignore(comment)
1738

    
1739
    cls._PARSE_SHOW = bnf
1740

    
1741
    return bnf
1742

    
1743
  @classmethod
1744
  def _GetShowData(cls, minor):
1745
    """Return the `drbdsetup show` data for a minor.
1746

1747
    """
1748
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1749
    if result.failed:
1750
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1751
      return None
1752
    return result.stdout
1753

    
1754
  @classmethod
1755
  def _GetDevInfo(cls, out):
1756
    """Parse details about a given DRBD minor.
1757

1758
    This return, if available, the local backing device (as a path)
1759
    and the local and remote (ip, port) information from a string
1760
    containing the output of the `drbdsetup show` command as returned
1761
    by _GetShowData.
1762

1763
    """
1764
    data = {}
1765
    if not out:
1766
      return data
1767

    
1768
    bnf = cls._GetShowParser()
1769
    # run pyparse
1770

    
1771
    try:
1772
      results = bnf.parseString(out)
1773
    except pyp.ParseException, err:
1774
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1775
                                    str(err))
1776

    
1777
    # and massage the results into our desired format
1778
    for section in results:
1779
      sname = section[0]
1780
      if sname == "_this_host":
1781
        for lst in section[1:]:
1782
          if lst[0] == "disk":
1783
            data["local_dev"] = lst[1]
1784
          elif lst[0] == "meta-disk":
1785
            data["meta_dev"] = lst[1]
1786
            data["meta_index"] = lst[2]
1787
          elif lst[0] == "address":
1788
            data["local_addr"] = tuple(lst[1:])
1789
      elif sname == "_remote_host":
1790
        for lst in section[1:]:
1791
          if lst[0] == "address":
1792
            data["remote_addr"] = tuple(lst[1:])
1793
    return data
1794

    
1795
  def _MatchesLocal(self, info):
1796
    """Test if our local config matches with an existing device.
1797

1798
    The parameter should be as returned from `_GetDevInfo()`. This
1799
    method tests if our local backing device is the same as the one in
1800
    the info parameter, in effect testing if we look like the given
1801
    device.
1802

1803
    """
1804
    if self._children:
1805
      backend, meta = self._children
1806
    else:
1807
      backend = meta = None
1808

    
1809
    if backend is not None:
1810
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1811
    else:
1812
      retval = ("local_dev" not in info)
1813

    
1814
    if meta is not None:
1815
      retval = retval and ("meta_dev" in info and
1816
                           info["meta_dev"] == meta.dev_path)
1817
      retval = retval and ("meta_index" in info and
1818
                           info["meta_index"] == 0)
1819
    else:
1820
      retval = retval and ("meta_dev" not in info and
1821
                           "meta_index" not in info)
1822
    return retval
1823

    
1824
  def _MatchesNet(self, info):
1825
    """Test if our network config matches with an existing device.
1826

1827
    The parameter should be as returned from `_GetDevInfo()`. This
1828
    method tests if our network configuration is the same as the one
1829
    in the info parameter, in effect testing if we look like the given
1830
    device.
1831

1832
    """
1833
    if (((self._lhost is None and not ("local_addr" in info)) and
1834
         (self._rhost is None and not ("remote_addr" in info)))):
1835
      return True
1836

    
1837
    if self._lhost is None:
1838
      return False
1839

    
1840
    if not ("local_addr" in info and
1841
            "remote_addr" in info):
1842
      return False
1843

    
1844
    retval = (info["local_addr"] == (self._lhost, self._lport))
1845
    retval = (retval and
1846
              info["remote_addr"] == (self._rhost, self._rport))
1847
    return retval
1848

    
1849
  @classmethod
1850
  def _AssembleLocal(cls, minor, backend, meta):
1851
    """Configure the local part of a DRBD device.
1852

1853
    This is the first thing that must be done on an unconfigured DRBD
1854
    device. And it must be done only once.
1855

1856
    """
1857
    if not cls._IsValidMeta(meta):
1858
      return False
1859
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1860
                           backend, meta, "0", "-e", "detach",
1861
                           "--create-device"])
1862
    if result.failed:
1863
      logger.Error("Can't attach local disk: %s" % result.output)
1864
    return not result.failed
1865

    
1866
  @classmethod
1867
  def _AssembleNet(cls, minor, net_info, protocol,
1868
                   dual_pri=False, hmac=None, secret=None):
1869
    """Configure the network part of the device.
1870

1871
    """
1872
    lhost, lport, rhost, rport = net_info
1873
    if None in net_info:
1874
      # we don't want network connection and actually want to make
1875
      # sure its shutdown
1876
      return cls._ShutdownNet(minor)
1877

    
1878
    args = ["drbdsetup", cls._DevPath(minor), "net",
1879
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1880
            "-A", "discard-zero-changes",
1881
            "-B", "consensus",
1882
            ]
1883
    if dual_pri:
1884
      args.append("-m")
1885
    if hmac and secret:
1886
      args.extend(["-a", hmac, "-x", secret])
1887
    result = utils.RunCmd(args)
1888
    if result.failed:
1889
      logger.Error("Can't setup network for dbrd device: %s" %
1890
                   result.fail_reason)
1891
      return False
1892

    
1893
    timeout = time.time() + 10
1894
    ok = False
1895
    while time.time() < timeout:
1896
      info = cls._GetDevInfo(cls._GetShowData(minor))
1897
      if not "local_addr" in info or not "remote_addr" in info:
1898
        time.sleep(1)
1899
        continue
1900
      if (info["local_addr"] != (lhost, lport) or
1901
          info["remote_addr"] != (rhost, rport)):
1902
        time.sleep(1)
1903
        continue
1904
      ok = True
1905
      break
1906
    if not ok:
1907
      logger.Error("Timeout while configuring network")
1908
      return False
1909
    return True
1910

    
1911
  def AddChildren(self, devices):
1912
    """Add a disk to the DRBD device.
1913

1914
    """
1915
    if self.minor is None:
1916
      raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1917
    if len(devices) != 2:
1918
      raise errors.BlockDeviceError("Need two devices for AddChildren")
1919
    info = self._GetDevInfo(self._GetShowData(self.minor))
1920
    if "local_dev" in info:
1921
      raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1922
    backend, meta = devices
1923
    if backend.dev_path is None or meta.dev_path is None:
1924
      raise errors.BlockDeviceError("Children not ready during AddChildren")
1925
    backend.Open()
1926
    meta.Open()
1927
    if not self._CheckMetaSize(meta.dev_path):
1928
      raise errors.BlockDeviceError("Invalid meta device size")
1929
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1930
    if not self._IsValidMeta(meta.dev_path):
1931
      raise errors.BlockDeviceError("Cannot initalize meta device")
1932

    
1933
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1934
      raise errors.BlockDeviceError("Can't attach to local storage")
1935
    self._children = devices
1936

    
1937
  def RemoveChildren(self, devices):
1938
    """Detach the drbd device from local storage.
1939

1940
    """
1941
    if self.minor is None:
1942
      raise errors.BlockDeviceError("Can't attach to drbd8 during"
1943
                                    " RemoveChildren")
1944
    # early return if we don't actually have backing storage
1945
    info = self._GetDevInfo(self._GetShowData(self.minor))
1946
    if "local_dev" not in info:
1947
      return
1948
    if len(self._children) != 2:
1949
      raise errors.BlockDeviceError("We don't have two children: %s" %
1950
                                    self._children)
1951
    if self._children.count(None) == 2: # we don't actually have children :)
1952
      logger.Error("Requested detach while detached")
1953
      return
1954
    if len(devices) != 2:
1955
      raise errors.BlockDeviceError("We need two children in RemoveChildren")
1956
    for child, dev in zip(self._children, devices):
1957
      if dev != child.dev_path:
1958
        raise errors.BlockDeviceError("Mismatch in local storage"
1959
                                      " (%s != %s) in RemoveChildren" %
1960
                                      (dev, child.dev_path))
1961

    
1962
    if not self._ShutdownLocal(self.minor):
1963
      raise errors.BlockDeviceError("Can't detach from local storage")
1964
    self._children = []
1965

    
1966
  def SetSyncSpeed(self, kbytes):
1967
    """Set the speed of the DRBD syncer.
1968

1969
    """
1970
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1971
    if self.minor is None:
1972
      logger.Info("Instance not attached to a device")
1973
      return False
1974
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1975
                           kbytes])
1976
    if result.failed:
1977
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1978
    return not result.failed and children_result
1979

    
1980
  def GetSyncStatus(self):
1981
    """Returns the sync status of the device.
1982

1983
    Returns:
1984
     (sync_percent, estimated_time, is_degraded)
1985

1986
    If sync_percent is None, it means all is ok
1987
    If estimated_time is None, it means we can't esimate
1988
    the time needed, otherwise it's the time left in seconds.
1989

1990

1991
    We set the is_degraded parameter to True on two conditions:
1992
    network not connected or local disk missing.
1993

1994
    We compute the ldisk parameter based on wheter we have a local
1995
    disk or not.
1996

1997
    """
1998
    if self.minor is None and not self.Attach():
1999
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
2000
    proc_info = self._MassageProcData(self._GetProcData())
2001
    if self.minor not in proc_info:
2002
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
2003
                                    self.minor)
2004
    line = proc_info[self.minor]
2005
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
2006
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
2007
    if match:
2008
      sync_percent = float(match.group(1))
2009
      hours = int(match.group(2))
2010
      minutes = int(match.group(3))
2011
      seconds = int(match.group(4))
2012
      est_time = hours * 3600 + minutes * 60 + seconds
2013
    else:
2014
      sync_percent = None
2015
      est_time = None
2016
    match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
2017
    if not match:
2018
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
2019
                                    self.minor)
2020
    client_state = match.group(1)
2021
    local_disk_state = match.group(2)
2022
    ldisk = local_disk_state != "UpToDate"
2023
    is_degraded = client_state != "Connected"
2024
    return sync_percent, est_time, is_degraded or ldisk, ldisk
2025

    
2026
  def GetStatus(self):
2027
    """Compute the status of the DRBD device
2028

2029
    Note that DRBD devices don't have the STATUS_EXISTING state.
2030

2031
    """
2032
    if self.minor is None and not self.Attach():
2033
      return self.STATUS_UNKNOWN
2034

    
2035
    data = self._GetProcData()
2036
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
2037
                       self.minor)
2038
    for line in data:
2039
      mresult = match.match(line)
2040
      if mresult:
2041
        break
2042
    else:
2043
      logger.Error("Can't find myself!")
2044
      return self.STATUS_UNKNOWN
2045

    
2046
    state = mresult.group(2)
2047
    if state == "Primary":
2048
      result = self.STATUS_ONLINE
2049
    else:
2050
      result = self.STATUS_STANDBY
2051

    
2052
    return result
2053

    
2054
  def Open(self, force=False):
2055
    """Make the local state primary.
2056

2057
    If the 'force' parameter is given, the '--do-what-I-say' parameter
2058
    is given. Since this is a pottentialy dangerous operation, the
2059
    force flag should be only given after creation, when it actually
2060
    has to be given.
2061

2062
    """
2063
    if self.minor is None and not self.Attach():
2064
      logger.Error("DRBD cannot attach to a device during open")
2065
      return False
2066
    cmd = ["drbdsetup", self.dev_path, "primary"]
2067
    if force:
2068
      cmd.append("-o")
2069
    result = utils.RunCmd(cmd)
2070
    if result.failed:
2071
      logger.Error("Can't make drbd device primary: %s" % result.output)
2072
      return False
2073
    return True
2074

    
2075
  def Close(self):
2076
    """Make the local state secondary.
2077

2078
    This will, of course, fail if the device is in use.
2079

2080
    """
2081
    if self.minor is None and not self.Attach():
2082
      logger.Info("Instance not attached to a device")
2083
      raise errors.BlockDeviceError("Can't find device")
2084
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2085
    if result.failed:
2086
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
2087
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
2088

    
2089
  def Attach(self):
2090
    """Find a DRBD device which matches our config and attach to it.
2091

2092
    In case of partially attached (local device matches but no network
2093
    setup), we perform the network attach. If successful, we re-test
2094
    the attach if can return success.
2095

2096
    """
2097
    for minor in self._GetUsedDevs():
2098
      info = self._GetDevInfo(self._GetShowData(minor))
2099
      match_l = self._MatchesLocal(info)
2100
      match_r = self._MatchesNet(info)
2101
      if match_l and match_r:
2102
        break
2103
      if match_l and not match_r and "local_addr" not in info:
2104
        res_r = self._AssembleNet(minor,
2105
                                  (self._lhost, self._lport,
2106
                                   self._rhost, self._rport),
2107
                                  "C")
2108
        if res_r:
2109
          if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2110
            break
2111
      # the weakest case: we find something that is only net attached
2112
      # even though we were passed some children at init time
2113
      if match_r and "local_dev" not in info:
2114
        break
2115
      if match_l and not match_r and "local_addr" in info:
2116
        # strange case - the device network part points to somewhere
2117
        # else, even though its local storage is ours; as we own the
2118
        # drbd space, we try to disconnect from the remote peer and
2119
        # reconnect to our correct one
2120
        if not self._ShutdownNet(minor):
2121
          raise errors.BlockDeviceError("Device has correct local storage,"
2122
                                        " wrong remote peer and is unable to"
2123
                                        " disconnect in order to attach to"
2124
                                        " the correct peer")
2125
        # note: _AssembleNet also handles the case when we don't want
2126
        # local storage (i.e. one or more of the _[lr](host|port) is
2127
        # None)
2128
        if (self._AssembleNet(minor, (self._lhost, self._lport,
2129
                                      self._rhost, self._rport), "C") and
2130
            self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2131
          break
2132

    
2133
    else:
2134
      minor = None
2135

    
2136
    self._SetFromMinor(minor)
2137
    return minor is not None
2138

    
2139
  def Assemble(self):
2140
    """Assemble the drbd.
2141

2142
    Method:
2143
      - if we have a local backing device, we bind to it by:
2144
        - checking the list of used drbd devices
2145
        - check if the local minor use of any of them is our own device
2146
        - if yes, abort?
2147
        - if not, bind
2148
      - if we have a local/remote net info:
2149
        - redo the local backing device step for the remote device
2150
        - check if any drbd device is using the local port,
2151
          if yes abort
2152
        - check if any remote drbd device is using the remote
2153
          port, if yes abort (for now)
2154
        - bind our net port
2155
        - bind the remote net port
2156

2157
    """
2158
    self.Attach()
2159
    if self.minor is not None:
2160
      logger.Info("Already assembled")
2161
      return True
2162

    
2163
    result = super(DRBD8, self).Assemble()
2164
    if not result:
2165
      return result
2166

    
2167
    minor = self._FindUnusedMinor()
2168
    need_localdev_teardown = False
2169
    if self._children and self._children[0] and self._children[1]:
2170
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2171
                                   self._children[1].dev_path)
2172
      if not result:
2173
        return False
2174
      need_localdev_teardown = True
2175
    if self._lhost and self._lport and self._rhost and self._rport:
2176
      result = self._AssembleNet(minor,
2177
                                 (self._lhost, self._lport,
2178
                                  self._rhost, self._rport),
2179
                                 "C")
2180
      if not result:
2181
        if need_localdev_teardown:
2182
          # we will ignore failures from this
2183
          logger.Error("net setup failed, tearing down local device")
2184
          self._ShutdownAll(minor)
2185
        return False
2186
    self._SetFromMinor(minor)
2187
    return True
2188

    
2189
  @classmethod
2190
  def _ShutdownLocal(cls, minor):
2191
    """Detach from the local device.
2192

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

2196
    """
2197
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2198
    if result.failed:
2199
      logger.Error("Can't detach local device: %s" % result.output)
2200
    return not result.failed
2201

    
2202
  @classmethod
2203
  def _ShutdownNet(cls, minor):
2204
    """Disconnect from the remote peer.
2205

2206
    This fails if we don't have a local device.
2207

2208
    """
2209
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2210
    if result.failed:
2211
      logger.Error("Can't shutdown network: %s" % result.output)
2212
    return not result.failed
2213

    
2214
  @classmethod
2215
  def _ShutdownAll(cls, minor):
2216
    """Deactivate the device.
2217

2218
    This will, of course, fail if the device is in use.
2219

2220
    """
2221
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2222
    if result.failed:
2223
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2224
    return not result.failed
2225

    
2226
  def Shutdown(self):
2227
    """Shutdown the DRBD device.
2228

2229
    """
2230
    if self.minor is None and not self.Attach():
2231
      logger.Info("DRBD device not attached to a device during Shutdown")
2232
      return True
2233
    if not self._ShutdownAll(self.minor):
2234
      return False
2235
    self.minor = None
2236
    self.dev_path = None
2237
    return True
2238

    
2239
  def Remove(self):
2240
    """Stub remove for DRBD devices.
2241

2242
    """
2243
    return self.Shutdown()
2244

    
2245
  @classmethod
2246
  def Create(cls, unique_id, children, size):
2247
    """Create a new DRBD8 device.
2248

2249
    Since DRBD devices are not created per se, just assembled, this
2250
    function only initializes the metadata.
2251

2252
    """
2253
    if len(children) != 2:
2254
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2255
    meta = children[1]
2256
    meta.Assemble()
2257
    if not meta.Attach():
2258
      raise errors.BlockDeviceError("Can't attach to meta device")
2259
    if not cls._CheckMetaSize(meta.dev_path):
2260
      raise errors.BlockDeviceError("Invalid meta device size")
2261
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2262
    if not cls._IsValidMeta(meta.dev_path):
2263
      raise errors.BlockDeviceError("Cannot initalize meta device")
2264
    return cls(unique_id, children)
2265

    
2266

    
2267
DEV_MAP = {
2268
  constants.LD_LV: LogicalVolume,
2269
  constants.LD_MD_R1: MDRaid1,
2270
  constants.LD_DRBD7: DRBDev,
2271
  constants.LD_DRBD8: DRBD8,
2272
  }
2273

    
2274

    
2275
def FindDevice(dev_type, unique_id, children):
2276
  """Search for an existing, assembled device.
2277

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

2281
  """
2282
  if dev_type not in DEV_MAP:
2283
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2284
  device = DEV_MAP[dev_type](unique_id, children)
2285
  if not device.Attach():
2286
    return None
2287
  return  device
2288

    
2289

    
2290
def AttachOrAssemble(dev_type, unique_id, children):
2291
  """Try to attach or assemble an existing device.
2292

2293
  This will attach to an existing assembled device or will assemble
2294
  the device, as needed, to bring it fully up.
2295

2296
  """
2297
  if dev_type not in DEV_MAP:
2298
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2299
  device = DEV_MAP[dev_type](unique_id, children)
2300
  if not device.Attach():
2301
    device.Assemble()
2302
    if not device.Attach():
2303
      raise errors.BlockDeviceError("Can't find a valid block device for"
2304
                                    " %s/%s/%s" %
2305
                                    (dev_type, unique_id, children))
2306
  return device
2307

    
2308

    
2309
def Create(dev_type, unique_id, children, size):
2310
  """Create a device.
2311

2312
  """
2313
  if dev_type not in DEV_MAP:
2314
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2315
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2316
  return device