Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 9db6dbce

History | View | Annotate | Download (70.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
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)
224

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

232
    """
233
    return None, None, False
234

    
235

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

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

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

    
259

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

263
    Only supported for some device types.
264

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

    
269

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

    
275

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

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

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

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

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

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

    
308
    pvlist = [ pv[1] for pv in pvs_info ]
309
    free_size = sum([ pv[0] for pv in pvs_info ])
310

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

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

326
    Args:
327
      vg_name: the volume group name
328

329
    Returns:
330
      list of (free_space, name) with free_space in mebibytes
331

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

    
351
    return data
352

    
353
  def Remove(self):
354
    """Remove this logical volume.
355

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

    
365
    return not result.failed
366

    
367
  def Rename(self, new_id):
368
    """Rename this logical volume.
369

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

    
385

    
386
  def Attach(self):
387
    """Attach to an existing LV.
388

389
    This method will try to see if an existing and active LV exists
390
    which matches the our name. If so, its major/minor will be
391
    recorded.
392

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

    
408
  def Assemble(self):
409
    """Assemble the device.
410

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

414
    """
415
    return True
416

    
417
  def Shutdown(self):
418
    """Shutdown the device.
419

420
    This is a no-op for the LV device type, as we don't deactivate the
421
    volumes on shutdown.
422

423
    """
424
    return True
425

    
426
  def GetStatus(self):
427
    """Return the status of the device.
428

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

433
    """
434
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
435
    if result.failed:
436
      logger.Error("Can't display lv: %s" % result.fail_reason)
437
      return self.STATUS_UNKNOWN
438
    out = result.stdout.strip()
439
    # format: type/permissions/alloc/fixed_minor/state/open
440
    if len(out) != 6:
441
      return self.STATUS_UNKNOWN
442
    #writable = (out[1] == "w")
443
    active = (out[4] == "a")
444
    online = (out[5] == "o")
445
    if online:
446
      retval = self.STATUS_ONLINE
447
    elif active:
448
      retval = self.STATUS_STANDBY
449
    else:
450
      retval = self.STATUS_EXISTING
451

    
452
    return retval
453

    
454
  def GetSyncStatus(self):
455
    """Returns the sync status of the device.
456

457
    If this device is a mirroring device, this function returns the
458
    status of the mirror.
459

460
    Returns:
461
     (sync_percent, estimated_time, is_degraded)
462

463
    For logical volumes, sync_percent and estimated_time are always
464
    None (no recovery in progress, as we don't handle the mirrored LV
465
    case).
466

467
    For the is_degraded parameter, we check if the logical volume has
468
    the 'virtual' type, which means it's not backed by existing
469
    storage anymore (read from it return I/O error). This happens
470
    after a physical disk failure and subsequent 'vgreduce
471
    --removemissing' on the volume group.
472

473
    """
474
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
475
    if result.failed:
476
      logger.Error("Can't display lv: %s" % result.fail_reason)
477
      return None, None, True
478
    out = result.stdout.strip()
479
    # format: type/permissions/alloc/fixed_minor/state/open
480
    if len(out) != 6:
481
      return None, None, True
482
    is_degraded = out[0] == 'v' # virtual volume, i.e. doesn't have
483
                                # backing storage
484
    return None, None, is_degraded
485

    
486
  def Open(self, force=False):
487
    """Make the device ready for I/O.
488

489
    This is a no-op for the LV device type.
490

491
    """
492
    return True
493

    
494
  def Close(self):
495
    """Notifies that the device will no longer be used for I/O.
496

497
    This is a no-op for the LV device type.
498

499
    """
500
    return True
501

    
502
  def Snapshot(self, size):
503
    """Create a snapshot copy of an lvm block device.
504

505
    """
506
    snap_name = self._lv_name + ".snap"
507

    
508
    # remove existing snapshot if found
509
    snap = LogicalVolume((self._vg_name, snap_name), None)
510
    snap.Remove()
511

    
512
    pvs_info = self.GetPVInfo(self._vg_name)
513
    if not pvs_info:
514
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
515
                                    self._vg_name)
516
    pvs_info.sort()
517
    pvs_info.reverse()
518
    free_size, pv_name = pvs_info[0]
519
    if free_size < size:
520
      raise errors.BlockDeviceError("Not enough free space: required %s,"
521
                                    " available %s" % (size, free_size))
522

    
523
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
524
                           "-n%s" % snap_name, self.dev_path])
525
    if result.failed:
526
      raise errors.BlockDeviceError("command: %s error: %s" %
527
                                    (result.cmd, result.fail_reason))
528

    
529
    return snap_name
530

    
531
  def SetInfo(self, text):
532
    """Update metadata with info text.
533

534
    """
535
    BlockDev.SetInfo(self, text)
536

    
537
    # Replace invalid characters
538
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
539
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
540

    
541
    # Only up to 128 characters are allowed
542
    text = text[:128]
543

    
544
    result = utils.RunCmd(["lvchange", "--addtag", text,
545
                           self.dev_path])
546
    if result.failed:
547
      raise errors.BlockDeviceError("Command: %s error: %s" %
548
                                    (result.cmd, result.fail_reason))
549

    
550

    
551
class MDRaid1(BlockDev):
552
  """raid1 device implemented via md.
553

554
  """
555
  def __init__(self, unique_id, children):
556
    super(MDRaid1, self).__init__(unique_id, children)
557
    self.major = 9
558
    self.Attach()
559

    
560
  def Attach(self):
561
    """Find an array which matches our config and attach to it.
562

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

565
    """
566
    minor = self._FindMDByUUID(self.unique_id)
567
    if minor is not None:
568
      self._SetFromMinor(minor)
569
    else:
570
      self.minor = None
571
      self.dev_path = None
572

    
573
    return (minor is not None)
574

    
575
  @staticmethod
576
  def _GetUsedDevs():
577
    """Compute the list of in-use MD devices.
578

579
    It doesn't matter if the used device have other raid level, just
580
    that they are in use.
581

582
    """
583
    mdstat = open("/proc/mdstat", "r")
584
    data = mdstat.readlines()
585
    mdstat.close()
586

    
587
    used_md = {}
588
    valid_line = re.compile("^md([0-9]+) : .*$")
589
    for line in data:
590
      match = valid_line.match(line)
591
      if match:
592
        md_no = int(match.group(1))
593
        used_md[md_no] = line
594

    
595
    return used_md
596

    
597
  @staticmethod
598
  def _GetDevInfo(minor):
599
    """Get info about a MD device.
600

601
    Currently only uuid is returned.
602

603
    """
604
    result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
605
    if result.failed:
606
      logger.Error("Can't display md: %s" % result.fail_reason)
607
      return None
608
    retval = {}
609
    for line in result.stdout.splitlines():
610
      line = line.strip()
611
      kv = line.split(" : ", 1)
612
      if kv:
613
        if kv[0] == "UUID":
614
          retval["uuid"] = kv[1].split()[0]
615
        elif kv[0] == "State":
616
          retval["state"] = kv[1].split(", ")
617
    return retval
618

    
619
  @staticmethod
620
  def _FindUnusedMinor():
621
    """Compute an unused MD minor.
622

623
    This code assumes that there are 256 minors only.
624

625
    """
626
    used_md = MDRaid1._GetUsedDevs()
627
    i = 0
628
    while i < 256:
629
      if i not in used_md:
630
        break
631
      i += 1
632
    if i == 256:
633
      logger.Error("Critical: Out of md minor numbers.")
634
      raise errors.BlockDeviceError("Can't find a free MD minor")
635
    return i
636

    
637
  @classmethod
638
  def _FindMDByUUID(cls, uuid):
639
    """Find the minor of an MD array with a given UUID.
640

641
    """
642
    md_list = cls._GetUsedDevs()
643
    for minor in md_list:
644
      info = cls._GetDevInfo(minor)
645
      if info and info["uuid"] == uuid:
646
        return minor
647
    return None
648

    
649
  @staticmethod
650
  def _ZeroSuperblock(dev_path):
651
    """Zero the possible locations for an MD superblock.
652

653
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
654
    fails in versions 2.x with the same error code as non-writable
655
    device.
656

657
    The superblocks are located at (negative values are relative to
658
    the end of the block device):
659
      - -128k to end for version 0.90 superblock
660
      - -8k to -12k for version 1.0 superblock (included in the above)
661
      - 0k to 4k for version 1.1 superblock
662
      - 4k to 8k for version 1.2 superblock
663

664
    To cover all situations, the zero-ing will be:
665
      - 0k to 128k
666
      - -128k to end
667

668
    As such, the minimum device size must be 128k, otherwise we'll get
669
    I/O errors.
670

671
    Note that this function depends on the fact that one can open,
672
    read and write block devices normally.
673

674
    """
675
    overwrite_size = 128 * 1024
676
    empty_buf = '\0' * overwrite_size
677
    fd = open(dev_path, "r+")
678
    try:
679
      fd.seek(0, 0)
680
      p1 = fd.tell()
681
      fd.write(empty_buf)
682
      p2 = fd.tell()
683
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
684
      fd.seek(-overwrite_size, 2)
685
      p1 = fd.tell()
686
      fd.write(empty_buf)
687
      p2 = fd.tell()
688
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
689
    finally:
690
      fd.close()
691

    
692
  @classmethod
693
  def Create(cls, unique_id, children, size):
694
    """Create a new MD raid1 array.
695

696
    """
697
    if not isinstance(children, (tuple, list)):
698
      raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
699
                       str(children))
700
    for i in children:
701
      if not isinstance(i, BlockDev):
702
        raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
703
    for i in children:
704
      try:
705
        cls._ZeroSuperblock(i.dev_path)
706
      except EnvironmentError, err:
707
        logger.Error("Can't zero superblock for %s: %s" %
708
                     (i.dev_path, str(err)))
709
        return None
710
    minor = cls._FindUnusedMinor()
711
    result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
712
                           "--auto=yes", "--force", "-l1",
713
                           "-n%d" % len(children)] +
714
                          [dev.dev_path for dev in children])
715

    
716
    if result.failed:
717
      logger.Error("Can't create md: %s: %s" % (result.fail_reason,
718
                                                result.output))
719
      return None
720
    info = cls._GetDevInfo(minor)
721
    if not info or not "uuid" in info:
722
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
723
      return None
724
    return MDRaid1(info["uuid"], children)
725

    
726
  def Remove(self):
727
    """Stub remove function for MD RAID 1 arrays.
728

729
    We don't remove the superblock right now. Mark a to do.
730

731
    """
732
    #TODO: maybe zero superblock on child devices?
733
    return self.Shutdown()
734

    
735
  def Rename(self, new_id):
736
    """Rename a device.
737

738
    This is not supported for md raid1 devices.
739

740
    """
741
    raise errors.ProgrammerError("Can't rename a md raid1 device")
742

    
743
  def AddChildren(self, devices):
744
    """Add new member(s) to the md raid1.
745

746
    """
747
    if self.minor is None and not self.Attach():
748
      raise errors.BlockDeviceError("Can't attach to device")
749

    
750
    args = ["mdadm", "-a", self.dev_path]
751
    for dev in devices:
752
      if dev.dev_path is None:
753
        raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
754
      dev.Open()
755
      args.append(dev.dev_path)
756
    result = utils.RunCmd(args)
757
    if result.failed:
758
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
759
                                    result.output)
760
    new_len = len(self._children) + len(devices)
761
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
762
    if result.failed:
763
      raise errors.BlockDeviceError("Can't grow md array: %s" %
764
                                    result.output)
765
    self._children.extend(devices)
766

    
767
  def RemoveChildren(self, devices):
768
    """Remove member(s) from the md raid1.
769

770
    """
771
    if self.minor is None and not self.Attach():
772
      raise errors.BlockDeviceError("Can't attach to device")
773
    new_len = len(self._children) - len(devices)
774
    if new_len < 1:
775
      raise errors.BlockDeviceError("Can't reduce to less than one child")
776
    args = ["mdadm", "-f", self.dev_path]
777
    orig_devs = []
778
    for dev in devices:
779
      args.append(dev)
780
      for c in self._children:
781
        if c.dev_path == dev:
782
          orig_devs.append(c)
783
          break
784
      else:
785
        raise errors.BlockDeviceError("Can't find device '%s' for removal" %
786
                                      dev)
787
    result = utils.RunCmd(args)
788
    if result.failed:
789
      raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
790
                                    result.output)
791

    
792
    # it seems here we need a short delay for MD to update its
793
    # superblocks
794
    time.sleep(0.5)
795
    args[1] = "-r"
796
    result = utils.RunCmd(args)
797
    if result.failed:
798
      raise errors.BlockDeviceError("Failed to remove device(s) from array:"
799
                                    " %s" % result.output)
800
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
801
                           "-n", new_len])
802
    if result.failed:
803
      raise errors.BlockDeviceError("Can't shrink md array: %s" %
804
                                    result.output)
805
    for dev in orig_devs:
806
      self._children.remove(dev)
807

    
808
  def GetStatus(self):
809
    """Return the status of the device.
810

811
    """
812
    self.Attach()
813
    if self.minor is None:
814
      retval = self.STATUS_UNKNOWN
815
    else:
816
      retval = self.STATUS_ONLINE
817
    return retval
818

    
819
  def _SetFromMinor(self, minor):
820
    """Set our parameters based on the given minor.
821

822
    This sets our minor variable and our dev_path.
823

824
    """
825
    self.minor = minor
826
    self.dev_path = "/dev/md%d" % minor
827

    
828
  def Assemble(self):
829
    """Assemble the MD device.
830

831
    At this point we should have:
832
      - list of children devices
833
      - uuid
834

835
    """
836
    result = super(MDRaid1, self).Assemble()
837
    if not result:
838
      return result
839
    md_list = self._GetUsedDevs()
840
    for minor in md_list:
841
      info = self._GetDevInfo(minor)
842
      if info and info["uuid"] == self.unique_id:
843
        self._SetFromMinor(minor)
844
        logger.Info("MD array %s already started" % str(self))
845
        return True
846
    free_minor = self._FindUnusedMinor()
847
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
848
                           self.unique_id, "/dev/md%d" % free_minor] +
849
                          [bdev.dev_path for bdev in self._children])
850
    if result.failed:
851
      logger.Error("Can't assemble MD array: %s: %s" %
852
                   (result.fail_reason, result.output))
853
      self.minor = None
854
    else:
855
      self.minor = free_minor
856
    return not result.failed
857

    
858
  def Shutdown(self):
859
    """Tear down the MD array.
860

861
    This does a 'mdadm --stop' so after this command, the array is no
862
    longer available.
863

864
    """
865
    if self.minor is None and not self.Attach():
866
      logger.Info("MD object not attached to a device")
867
      return True
868

    
869
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
870
    if result.failed:
871
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
872
      return False
873
    self.minor = None
874
    self.dev_path = None
875
    return True
876

    
877
  def SetSyncSpeed(self, kbytes):
878
    """Set the maximum sync speed for the MD array.
879

880
    """
881
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
882
    if self.minor is None:
883
      logger.Error("MD array not attached to a device")
884
      return False
885
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
886
    try:
887
      f.write("%d" % kbytes)
888
    finally:
889
      f.close()
890
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
891
    try:
892
      f.write("%d" % (kbytes/2))
893
    finally:
894
      f.close()
895
    return result
896

    
897
  def GetSyncStatus(self):
898
    """Returns the sync status of the device.
899

900
    Returns:
901
     (sync_percent, estimated_time, is_degraded)
902

903
    If sync_percent is None, it means all is ok
904
    If estimated_time is None, it means we can't esimate
905
    the time needed, otherwise it's the time left in seconds
906

907
    """
908
    if self.minor is None and not self.Attach():
909
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
910
    dev_info = self._GetDevInfo(self.minor)
911
    is_clean = ("state" in dev_info and
912
                len(dev_info["state"]) == 1 and
913
                dev_info["state"][0] in ("clean", "active"))
914
    sys_path = "/sys/block/md%s/md/" % self.minor
915
    f = file(sys_path + "sync_action")
916
    sync_status = f.readline().strip()
917
    f.close()
918
    if sync_status == "idle":
919
      return None, None, not is_clean
920
    f = file(sys_path + "sync_completed")
921
    sync_completed = f.readline().strip().split(" / ")
922
    f.close()
923
    if len(sync_completed) != 2:
924
      return 0, None, not is_clean
925
    sync_done, sync_total = [float(i) for i in sync_completed]
926
    sync_percent = 100.0*sync_done/sync_total
927
    f = file(sys_path + "sync_speed")
928
    sync_speed_k = int(f.readline().strip())
929
    if sync_speed_k == 0:
930
      time_est = None
931
    else:
932
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
933
    return sync_percent, time_est, not is_clean
934

    
935
  def Open(self, force=False):
936
    """Make the device ready for I/O.
937

938
    This is a no-op for the MDRaid1 device type, although we could use
939
    the 2.6.18's new array_state thing.
940

941
    """
942
    return True
943

    
944
  def Close(self):
945
    """Notifies that the device will no longer be used for I/O.
946

947
    This is a no-op for the MDRaid1 device type, but see comment for
948
    `Open()`.
949

950
    """
951
    return True
952

    
953

    
954
class BaseDRBD(BlockDev):
955
  """Base DRBD class.
956

957
  This class contains a few bits of common functionality between the
958
  0.7 and 8.x versions of DRBD.
959

960
  """
961
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
962
                           r" \(api:(\d+)/proto:(\d+)\)")
963
  _DRBD_MAJOR = 147
964
  _ST_UNCONFIGURED = "Unconfigured"
965
  _ST_WFCONNECTION = "WFConnection"
966
  _ST_CONNECTED = "Connected"
967

    
968
  @staticmethod
969
  def _GetProcData():
970
    """Return data from /proc/drbd.
971

972
    """
973
    stat = open("/proc/drbd", "r")
974
    try:
975
      data = stat.read().splitlines()
976
    finally:
977
      stat.close()
978
    if not data:
979
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
980
    return data
981

    
982
  @staticmethod
983
  def _MassageProcData(data):
984
    """Transform the output of _GetProdData into a nicer form.
985

986
    Returns:
987
      a dictionary of minor: joined lines from /proc/drbd for that minor
988

989
    """
990
    lmatch = re.compile("^ *([0-9]+):.*$")
991
    results = {}
992
    old_minor = old_line = None
993
    for line in data:
994
      lresult = lmatch.match(line)
995
      if lresult is not None:
996
        if old_minor is not None:
997
          results[old_minor] = old_line
998
        old_minor = int(lresult.group(1))
999
        old_line = line
1000
      else:
1001
        if old_minor is not None:
1002
          old_line += " " + line.strip()
1003
    # add last line
1004
    if old_minor is not None:
1005
      results[old_minor] = old_line
1006
    return results
1007

    
1008
  @classmethod
1009
  def _GetVersion(cls):
1010
    """Return the DRBD version.
1011

1012
    This will return a list [k_major, k_minor, k_point, api, proto].
1013

1014
    """
1015
    proc_data = cls._GetProcData()
1016
    first_line = proc_data[0].strip()
1017
    version = cls._VERSION_RE.match(first_line)
1018
    if not version:
1019
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1020
                                    first_line)
1021
    return [int(val) for val in version.groups()]
1022

    
1023
  @staticmethod
1024
  def _DevPath(minor):
1025
    """Return the path to a drbd device for a given minor.
1026

1027
    """
1028
    return "/dev/drbd%d" % minor
1029

    
1030
  @classmethod
1031
  def _GetUsedDevs(cls):
1032
    """Compute the list of used DRBD devices.
1033

1034
    """
1035
    data = cls._GetProcData()
1036

    
1037
    used_devs = {}
1038
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1039
    for line in data:
1040
      match = valid_line.match(line)
1041
      if not match:
1042
        continue
1043
      minor = int(match.group(1))
1044
      state = match.group(2)
1045
      if state == cls._ST_UNCONFIGURED:
1046
        continue
1047
      used_devs[minor] = state, line
1048

    
1049
    return used_devs
1050

    
1051
  def _SetFromMinor(self, minor):
1052
    """Set our parameters based on the given minor.
1053

1054
    This sets our minor variable and our dev_path.
1055

1056
    """
1057
    if minor is None:
1058
      self.minor = self.dev_path = None
1059
    else:
1060
      self.minor = minor
1061
      self.dev_path = self._DevPath(minor)
1062

    
1063
  @staticmethod
1064
  def _CheckMetaSize(meta_device):
1065
    """Check if the given meta device looks like a valid one.
1066

1067
    This currently only check the size, which must be around
1068
    128MiB.
1069

1070
    """
1071
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1072
    if result.failed:
1073
      logger.Error("Failed to get device size: %s" % result.fail_reason)
1074
      return False
1075
    try:
1076
      sectors = int(result.stdout)
1077
    except ValueError:
1078
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1079
      return False
1080
    bytes = sectors * 512
1081
    if bytes < 128 * 1024 * 1024: # less than 128MiB
1082
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1083
      return False
1084
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1085
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1086
      return False
1087
    return True
1088

    
1089
  def Rename(self, new_id):
1090
    """Rename a device.
1091

1092
    This is not supported for drbd devices.
1093

1094
    """
1095
    raise errors.ProgrammerError("Can't rename a drbd device")
1096

    
1097

    
1098
class DRBDev(BaseDRBD):
1099
  """DRBD block device.
1100

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

1105
  The unique_id for the drbd device is the (local_ip, local_port,
1106
  remote_ip, remote_port) tuple, and it must have two children: the
1107
  data device and the meta_device. The meta device is checked for
1108
  valid size and is zeroed on create.
1109

1110
  """
1111
  def __init__(self, unique_id, children):
1112
    super(DRBDev, self).__init__(unique_id, children)
1113
    self.major = self._DRBD_MAJOR
1114
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1115
    if kmaj != 0 and kmin != 7:
1116
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1117
                                    " requested ganeti usage: kernel is"
1118
                                    " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1119

    
1120
    if len(children) != 2:
1121
      raise ValueError("Invalid configuration data %s" % str(children))
1122
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1123
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1124
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1125
    self.Attach()
1126

    
1127
  @classmethod
1128
  def _FindUnusedMinor(cls):
1129
    """Find an unused DRBD device.
1130

1131
    """
1132
    data = cls._GetProcData()
1133

    
1134
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1135
    for line in data:
1136
      match = valid_line.match(line)
1137
      if match:
1138
        return int(match.group(1))
1139
    logger.Error("Error: no free drbd minors!")
1140
    raise errors.BlockDeviceError("Can't find a free DRBD minor")
1141

    
1142
  @classmethod
1143
  def _GetDevInfo(cls, minor):
1144
    """Get details about a given DRBD minor.
1145

1146
    This return, if available, the local backing device in (major,
1147
    minor) formant and the local and remote (ip, port) information.
1148

1149
    """
1150
    data = {}
1151
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1152
    if result.failed:
1153
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1154
      return data
1155
    out = result.stdout
1156
    if out == "Not configured\n":
1157
      return data
1158
    for line in out.splitlines():
1159
      if "local_dev" not in data:
1160
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1161
        if match:
1162
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1163
          continue
1164
      if "meta_dev" not in data:
1165
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1166
        if match:
1167
          if match.group(2) is not None and match.group(3) is not None:
1168
            # matched on the major/minor
1169
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1170
          else:
1171
            # matched on the "internal" string
1172
            data["meta_dev"] = match.group(1)
1173
            # in this case, no meta_index is in the output
1174
            data["meta_index"] = -1
1175
          continue
1176
      if "meta_index" not in data:
1177
        match = re.match("^Meta index: ([0-9]+).*$", line)
1178
        if match:
1179
          data["meta_index"] = int(match.group(1))
1180
          continue
1181
      if "local_addr" not in data:
1182
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1183
        if match:
1184
          data["local_addr"] = (match.group(1), int(match.group(2)))
1185
          continue
1186
      if "remote_addr" not in data:
1187
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1188
        if match:
1189
          data["remote_addr"] = (match.group(1), int(match.group(2)))
1190
          continue
1191
    return data
1192

    
1193
  def _MatchesLocal(self, info):
1194
    """Test if our local config matches with an existing device.
1195

1196
    The parameter should be as returned from `_GetDevInfo()`. This
1197
    method tests if our local backing device is the same as the one in
1198
    the info parameter, in effect testing if we look like the given
1199
    device.
1200

1201
    """
1202
    if not ("local_dev" in info and "meta_dev" in info and
1203
            "meta_index" in info):
1204
      return False
1205

    
1206
    backend = self._children[0]
1207
    if backend is not None:
1208
      retval = (info["local_dev"] == (backend.major, backend.minor))
1209
    else:
1210
      retval = (info["local_dev"] == (0, 0))
1211
    meta = self._children[1]
1212
    if meta is not None:
1213
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1214
      retval = retval and (info["meta_index"] == 0)
1215
    else:
1216
      retval = retval and (info["meta_dev"] == "internal" and
1217
                           info["meta_index"] == -1)
1218
    return retval
1219

    
1220
  def _MatchesNet(self, info):
1221
    """Test if our network config matches with an existing device.
1222

1223
    The parameter should be as returned from `_GetDevInfo()`. This
1224
    method tests if our network configuration is the same as the one
1225
    in the info parameter, in effect testing if we look like the given
1226
    device.
1227

1228
    """
1229
    if (((self._lhost is None and not ("local_addr" in info)) and
1230
         (self._rhost is None and not ("remote_addr" in info)))):
1231
      return True
1232

    
1233
    if self._lhost is None:
1234
      return False
1235

    
1236
    if not ("local_addr" in info and
1237
            "remote_addr" in info):
1238
      return False
1239

    
1240
    retval = (info["local_addr"] == (self._lhost, self._lport))
1241
    retval = (retval and
1242
              info["remote_addr"] == (self._rhost, self._rport))
1243
    return retval
1244

    
1245
  @classmethod
1246
  def _AssembleLocal(cls, minor, backend, meta):
1247
    """Configure the local part of a DRBD device.
1248

1249
    This is the first thing that must be done on an unconfigured DRBD
1250
    device. And it must be done only once.
1251

1252
    """
1253
    if not cls._CheckMetaSize(meta):
1254
      return False
1255
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1256
                           backend, meta, "0", "-e", "detach"])
1257
    if result.failed:
1258
      logger.Error("Can't attach local disk: %s" % result.output)
1259
    return not result.failed
1260

    
1261
  @classmethod
1262
  def _ShutdownLocal(cls, minor):
1263
    """Detach from the local device.
1264

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

1268
    """
1269
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1270
    if result.failed:
1271
      logger.Error("Can't detach local device: %s" % result.output)
1272
    return not result.failed
1273

    
1274
  @staticmethod
1275
  def _ShutdownAll(minor):
1276
    """Deactivate the device.
1277

1278
    This will, of course, fail if the device is in use.
1279

1280
    """
1281
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1282
    if result.failed:
1283
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1284
    return not result.failed
1285

    
1286
  @classmethod
1287
  def _AssembleNet(cls, minor, net_info, protocol):
1288
    """Configure the network part of the device.
1289

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

1297
    """
1298
    lhost, lport, rhost, rport = net_info
1299
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1300
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1301
                           protocol])
1302
    if result.failed:
1303
      logger.Error("Can't setup network for dbrd device: %s" %
1304
                   result.fail_reason)
1305
      return False
1306

    
1307
    timeout = time.time() + 10
1308
    ok = False
1309
    while time.time() < timeout:
1310
      info = cls._GetDevInfo(minor)
1311
      if not "local_addr" in info or not "remote_addr" in info:
1312
        time.sleep(1)
1313
        continue
1314
      if (info["local_addr"] != (lhost, lport) or
1315
          info["remote_addr"] != (rhost, rport)):
1316
        time.sleep(1)
1317
        continue
1318
      ok = True
1319
      break
1320
    if not ok:
1321
      logger.Error("Timeout while configuring network")
1322
      return False
1323
    return True
1324

    
1325
  @classmethod
1326
  def _ShutdownNet(cls, minor):
1327
    """Disconnect from the remote peer.
1328

1329
    This fails if we don't have a local device.
1330

1331
    """
1332
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1333
    logger.Error("Can't shutdown network: %s" % result.output)
1334
    return not result.failed
1335

    
1336
  def Assemble(self):
1337
    """Assemble the drbd.
1338

1339
    Method:
1340
      - if we have a local backing device, we bind to it by:
1341
        - checking the list of used drbd devices
1342
        - check if the local minor use of any of them is our own device
1343
        - if yes, abort?
1344
        - if not, bind
1345
      - if we have a local/remote net info:
1346
        - redo the local backing device step for the remote device
1347
        - check if any drbd device is using the local port,
1348
          if yes abort
1349
        - check if any remote drbd device is using the remote
1350
          port, if yes abort (for now)
1351
        - bind our net port
1352
        - bind the remote net port
1353

1354
    """
1355
    self.Attach()
1356
    if self.minor is not None:
1357
      logger.Info("Already assembled")
1358
      return True
1359

    
1360
    result = super(DRBDev, self).Assemble()
1361
    if not result:
1362
      return result
1363

    
1364
    minor = self._FindUnusedMinor()
1365
    need_localdev_teardown = False
1366
    if self._children[0]:
1367
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1368
                                   self._children[1].dev_path)
1369
      if not result:
1370
        return False
1371
      need_localdev_teardown = True
1372
    if self._lhost and self._lport and self._rhost and self._rport:
1373
      result = self._AssembleNet(minor,
1374
                                 (self._lhost, self._lport,
1375
                                  self._rhost, self._rport),
1376
                                 "C")
1377
      if not result:
1378
        if need_localdev_teardown:
1379
          # we will ignore failures from this
1380
          logger.Error("net setup failed, tearing down local device")
1381
          self._ShutdownAll(minor)
1382
        return False
1383
    self._SetFromMinor(minor)
1384
    return True
1385

    
1386
  def Shutdown(self):
1387
    """Shutdown the DRBD device.
1388

1389
    """
1390
    if self.minor is None and not self.Attach():
1391
      logger.Info("DRBD device not attached to a device during Shutdown")
1392
      return True
1393
    if not self._ShutdownAll(self.minor):
1394
      return False
1395
    self.minor = None
1396
    self.dev_path = None
1397
    return True
1398

    
1399
  def Attach(self):
1400
    """Find a DRBD device which matches our config and attach to it.
1401

1402
    In case of partially attached (local device matches but no network
1403
    setup), we perform the network attach. If successful, we re-test
1404
    the attach if can return success.
1405

1406
    """
1407
    for minor in self._GetUsedDevs():
1408
      info = self._GetDevInfo(minor)
1409
      match_l = self._MatchesLocal(info)
1410
      match_r = self._MatchesNet(info)
1411
      if match_l and match_r:
1412
        break
1413
      if match_l and not match_r and "local_addr" not in info:
1414
        res_r = self._AssembleNet(minor,
1415
                                  (self._lhost, self._lport,
1416
                                   self._rhost, self._rport),
1417
                                  "C")
1418
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1419
          break
1420
    else:
1421
      minor = None
1422

    
1423
    self._SetFromMinor(minor)
1424
    return minor is not None
1425

    
1426
  def Open(self, force=False):
1427
    """Make the local state primary.
1428

1429
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1430
    is given. Since this is a pottentialy dangerous operation, the
1431
    force flag should be only given after creation, when it actually
1432
    has to be given.
1433

1434
    """
1435
    if self.minor is None and not self.Attach():
1436
      logger.Error("DRBD cannot attach to a device during open")
1437
      return False
1438
    cmd = ["drbdsetup", self.dev_path, "primary"]
1439
    if force:
1440
      cmd.append("--do-what-I-say")
1441
    result = utils.RunCmd(cmd)
1442
    if result.failed:
1443
      logger.Error("Can't make drbd device primary: %s" % result.output)
1444
      return False
1445
    return True
1446

    
1447
  def Close(self):
1448
    """Make the local state secondary.
1449

1450
    This will, of course, fail if the device is in use.
1451

1452
    """
1453
    if self.minor is None and not self.Attach():
1454
      logger.Info("Instance not attached to a device")
1455
      raise errors.BlockDeviceError("Can't find device")
1456
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1457
    if result.failed:
1458
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1459
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1460

    
1461
  def SetSyncSpeed(self, kbytes):
1462
    """Set the speed of the DRBD syncer.
1463

1464
    """
1465
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1466
    if self.minor is None:
1467
      logger.Info("Instance not attached to a device")
1468
      return False
1469
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1470
                           kbytes])
1471
    if result.failed:
1472
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1473
    return not result.failed and children_result
1474

    
1475
  def GetSyncStatus(self):
1476
    """Returns the sync status of the device.
1477

1478
    Returns:
1479
     (sync_percent, estimated_time, is_degraded)
1480

1481
    If sync_percent is None, it means all is ok
1482
    If estimated_time is None, it means we can't esimate
1483
    the time needed, otherwise it's the time left in seconds
1484

1485
    """
1486
    if self.minor is None and not self.Attach():
1487
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1488
    proc_info = self._MassageProcData(self._GetProcData())
1489
    if self.minor not in proc_info:
1490
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1491
                                    self.minor)
1492
    line = proc_info[self.minor]
1493
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1494
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1495
    if match:
1496
      sync_percent = float(match.group(1))
1497
      hours = int(match.group(2))
1498
      minutes = int(match.group(3))
1499
      seconds = int(match.group(4))
1500
      est_time = hours * 3600 + minutes * 60 + seconds
1501
    else:
1502
      sync_percent = None
1503
      est_time = None
1504
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1505
    if not match:
1506
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1507
                                    self.minor)
1508
    client_state = match.group(1)
1509
    is_degraded = client_state != "Connected"
1510
    return sync_percent, est_time, is_degraded
1511

    
1512
  def GetStatus(self):
1513
    """Compute the status of the DRBD device
1514

1515
    Note that DRBD devices don't have the STATUS_EXISTING state.
1516

1517
    """
1518
    if self.minor is None and not self.Attach():
1519
      return self.STATUS_UNKNOWN
1520

    
1521
    data = self._GetProcData()
1522
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1523
                       self.minor)
1524
    for line in data:
1525
      mresult = match.match(line)
1526
      if mresult:
1527
        break
1528
    else:
1529
      logger.Error("Can't find myself!")
1530
      return self.STATUS_UNKNOWN
1531

    
1532
    state = mresult.group(2)
1533
    if state == "Primary":
1534
      result = self.STATUS_ONLINE
1535
    else:
1536
      result = self.STATUS_STANDBY
1537

    
1538
    return result
1539

    
1540
  @staticmethod
1541
  def _ZeroDevice(device):
1542
    """Zero a device.
1543

1544
    This writes until we get ENOSPC.
1545

1546
    """
1547
    f = open(device, "w")
1548
    buf = "\0" * 1048576
1549
    try:
1550
      while True:
1551
        f.write(buf)
1552
    except IOError, err:
1553
      if err.errno != errno.ENOSPC:
1554
        raise
1555

    
1556
  @classmethod
1557
  def Create(cls, unique_id, children, size):
1558
    """Create a new DRBD device.
1559

1560
    Since DRBD devices are not created per se, just assembled, this
1561
    function just zeroes the meta device.
1562

1563
    """
1564
    if len(children) != 2:
1565
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1566
    meta = children[1]
1567
    meta.Assemble()
1568
    if not meta.Attach():
1569
      raise errors.BlockDeviceError("Can't attach to meta device")
1570
    if not cls._CheckMetaSize(meta.dev_path):
1571
      raise errors.BlockDeviceError("Invalid meta device")
1572
    logger.Info("Started zeroing device %s" % meta.dev_path)
1573
    cls._ZeroDevice(meta.dev_path)
1574
    logger.Info("Done zeroing device %s" % meta.dev_path)
1575
    return cls(unique_id, children)
1576

    
1577
  def Remove(self):
1578
    """Stub remove for DRBD devices.
1579

1580
    """
1581
    return self.Shutdown()
1582

    
1583

    
1584
class DRBD8(BaseDRBD):
1585
  """DRBD v8.x block device.
1586

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

1591
  The unique_id for the drbd device is the (local_ip, local_port,
1592
  remote_ip, remote_port) tuple, and it must have two children: the
1593
  data device and the meta_device. The meta device is checked for
1594
  valid size and is zeroed on create.
1595

1596
  """
1597
  _MAX_MINORS = 255
1598
  _PARSE_SHOW = None
1599

    
1600
  def __init__(self, unique_id, children):
1601
    if children and children.count(None) > 0:
1602
      children = []
1603
    super(DRBD8, self).__init__(unique_id, children)
1604
    self.major = self._DRBD_MAJOR
1605
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1606
    if kmaj != 8:
1607
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1608
                                    " requested ganeti usage: kernel is"
1609
                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1610

    
1611
    if len(children) not in (0, 2):
1612
      raise ValueError("Invalid configuration data %s" % str(children))
1613
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1614
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1615
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1616
    self.Attach()
1617

    
1618
  @classmethod
1619
  def _InitMeta(cls, minor, dev_path):
1620
    """Initialize a meta device.
1621

1622
    This will not work if the given minor is in use.
1623

1624
    """
1625
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1626
                           "v08", dev_path, "0", "create-md"])
1627
    if result.failed:
1628
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1629
                                    result.output)
1630

    
1631
  @classmethod
1632
  def _FindUnusedMinor(cls):
1633
    """Find an unused DRBD device.
1634

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

1638
    """
1639
    data = cls._GetProcData()
1640

    
1641
    unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1642
    used_line = re.compile("^ *([0-9]+): cs:")
1643
    highest = None
1644
    for line in data:
1645
      match = unused_line.match(line)
1646
      if match:
1647
        return int(match.group(1))
1648
      match = used_line.match(line)
1649
      if match:
1650
        minor = int(match.group(1))
1651
        highest = max(highest, minor)
1652
    if highest is None: # there are no minors in use at all
1653
      return 0
1654
    if highest >= cls._MAX_MINORS:
1655
      logger.Error("Error: no free drbd minors!")
1656
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1657
    return highest + 1
1658

    
1659
  @classmethod
1660
  def _IsValidMeta(cls, meta_device):
1661
    """Check if the given meta device looks like a valid one.
1662

1663
    """
1664
    minor = cls._FindUnusedMinor()
1665
    minor_path = cls._DevPath(minor)
1666
    result = utils.RunCmd(["drbdmeta", minor_path,
1667
                           "v08", meta_device, "0",
1668
                           "dstate"])
1669
    if result.failed:
1670
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1671
      return False
1672
    return True
1673

    
1674
  @classmethod
1675
  def _GetShowParser(cls):
1676
    """Return a parser for `drbd show` output.
1677

1678
    This will either create or return an already-create parser for the
1679
    output of the command `drbd show`.
1680

1681
    """
1682
    if cls._PARSE_SHOW is not None:
1683
      return cls._PARSE_SHOW
1684

    
1685
    # pyparsing setup
1686
    lbrace = pyp.Literal("{").suppress()
1687
    rbrace = pyp.Literal("}").suppress()
1688
    semi = pyp.Literal(";").suppress()
1689
    # this also converts the value to an int
1690
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t:(l, [int(t[0])]))
1691

    
1692
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1693
    defa = pyp.Literal("_is_default").suppress()
1694
    dbl_quote = pyp.Literal('"').suppress()
1695

    
1696
    keyword = pyp.Word(pyp.alphanums + '-')
1697

    
1698
    # value types
1699
    value = pyp.Word(pyp.alphanums + '_-/.:')
1700
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1701
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1702
                 number)
1703
    # meta device, extended syntax
1704
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1705
                  number + pyp.Word(']').suppress())
1706

    
1707
    # a statement
1708
    stmt = (~rbrace + keyword + ~lbrace +
1709
            (addr_port ^ value ^ quoted ^ meta_value) +
1710
            pyp.Optional(defa) + semi +
1711
            pyp.Optional(pyp.restOfLine).suppress())
1712

    
1713
    # an entire section
1714
    section_name = pyp.Word(pyp.alphas + '_')
1715
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1716

    
1717
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1718
    bnf.ignore(comment)
1719

    
1720
    cls._PARSE_SHOW = bnf
1721

    
1722
    return bnf
1723

    
1724
  @classmethod
1725
  def _GetDevInfo(cls, minor):
1726
    """Get details about a given DRBD minor.
1727

1728
    This return, if available, the local backing device (as a path)
1729
    and the local and remote (ip, port) information.
1730

1731
    """
1732
    data = {}
1733
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1734
    if result.failed:
1735
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1736
      return data
1737
    out = result.stdout
1738
    if not out:
1739
      return data
1740

    
1741
    bnf = cls._GetShowParser()
1742
    # run pyparse
1743

    
1744
    try:
1745
      results = bnf.parseString(out)
1746
    except pyp.ParseException, err:
1747
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1748
                                    str(err))
1749

    
1750
    # and massage the results into our desired format
1751
    for section in results:
1752
      sname = section[0]
1753
      if sname == "_this_host":
1754
        for lst in section[1:]:
1755
          if lst[0] == "disk":
1756
            data["local_dev"] = lst[1]
1757
          elif lst[0] == "meta-disk":
1758
            data["meta_dev"] = lst[1]
1759
            data["meta_index"] = lst[2]
1760
          elif lst[0] == "address":
1761
            data["local_addr"] = tuple(lst[1:])
1762
      elif sname == "_remote_host":
1763
        for lst in section[1:]:
1764
          if lst[0] == "address":
1765
            data["remote_addr"] = tuple(lst[1:])
1766
    return data
1767

    
1768
  def _MatchesLocal(self, info):
1769
    """Test if our local config matches with an existing device.
1770

1771
    The parameter should be as returned from `_GetDevInfo()`. This
1772
    method tests if our local backing device is the same as the one in
1773
    the info parameter, in effect testing if we look like the given
1774
    device.
1775

1776
    """
1777
    if self._children:
1778
      backend, meta = self._children
1779
    else:
1780
      backend = meta = None
1781

    
1782
    if backend is not None:
1783
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1784
    else:
1785
      retval = ("local_dev" not in info)
1786

    
1787
    if meta is not None:
1788
      retval = retval and ("meta_dev" in info and
1789
                           info["meta_dev"] == meta.dev_path)
1790
      retval = retval and ("meta_index" in info and
1791
                           info["meta_index"] == 0)
1792
    else:
1793
      retval = retval and ("meta_dev" not in info and
1794
                           "meta_index" not in info)
1795
    return retval
1796

    
1797
  def _MatchesNet(self, info):
1798
    """Test if our network config matches with an existing device.
1799

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

1805
    """
1806
    if (((self._lhost is None and not ("local_addr" in info)) and
1807
         (self._rhost is None and not ("remote_addr" in info)))):
1808
      return True
1809

    
1810
    if self._lhost is None:
1811
      return False
1812

    
1813
    if not ("local_addr" in info and
1814
            "remote_addr" in info):
1815
      return False
1816

    
1817
    retval = (info["local_addr"] == (self._lhost, self._lport))
1818
    retval = (retval and
1819
              info["remote_addr"] == (self._rhost, self._rport))
1820
    return retval
1821

    
1822
  @classmethod
1823
  def _AssembleLocal(cls, minor, backend, meta):
1824
    """Configure the local part of a DRBD device.
1825

1826
    This is the first thing that must be done on an unconfigured DRBD
1827
    device. And it must be done only once.
1828

1829
    """
1830
    if not cls._IsValidMeta(meta):
1831
      return False
1832
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1833
                           backend, meta, "0", "-e", "detach",
1834
                           "--create-device"])
1835
    if result.failed:
1836
      logger.Error("Can't attach local disk: %s" % result.output)
1837
    return not result.failed
1838

    
1839
  @classmethod
1840
  def _AssembleNet(cls, minor, net_info, protocol,
1841
                   dual_pri=False, hmac=None, secret=None):
1842
    """Configure the network part of the device.
1843

1844
    """
1845
    lhost, lport, rhost, rport = net_info
1846
    args = ["drbdsetup", cls._DevPath(minor), "net",
1847
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1848
            "-A", "discard-zero-changes",
1849
            "-B", "consensus",
1850
            ]
1851
    if dual_pri:
1852
      args.append("-m")
1853
    if hmac and secret:
1854
      args.extend(["-a", hmac, "-x", secret])
1855
    result = utils.RunCmd(args)
1856
    if result.failed:
1857
      logger.Error("Can't setup network for dbrd device: %s" %
1858
                   result.fail_reason)
1859
      return False
1860

    
1861
    timeout = time.time() + 10
1862
    ok = False
1863
    while time.time() < timeout:
1864
      info = cls._GetDevInfo(minor)
1865
      if not "local_addr" in info or not "remote_addr" in info:
1866
        time.sleep(1)
1867
        continue
1868
      if (info["local_addr"] != (lhost, lport) or
1869
          info["remote_addr"] != (rhost, rport)):
1870
        time.sleep(1)
1871
        continue
1872
      ok = True
1873
      break
1874
    if not ok:
1875
      logger.Error("Timeout while configuring network")
1876
      return False
1877
    return True
1878

    
1879
  def AddChildren(self, devices):
1880
    """Add a disk to the DRBD device.
1881

1882
    """
1883
    if self.minor is None:
1884
      raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1885
    if len(devices) != 2:
1886
      raise errors.BlockDeviceError("Need two devices for AddChildren")
1887
    info = self._GetDevInfo(self.minor)
1888
    if "local_dev" in info:
1889
      raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1890
    backend, meta = devices
1891
    if backend.dev_path is None or meta.dev_path is None:
1892
      raise errors.BlockDeviceError("Children not ready during AddChildren")
1893
    backend.Open()
1894
    meta.Open()
1895
    if not self._CheckMetaSize(meta.dev_path):
1896
      raise errors.BlockDeviceError("Invalid meta device size")
1897
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1898
    if not self._IsValidMeta(meta.dev_path):
1899
      raise errors.BlockDeviceError("Cannot initalize meta device")
1900

    
1901
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1902
      raise errors.BlockDeviceError("Can't attach to local storage")
1903
    self._children = devices
1904

    
1905
  def RemoveChildren(self, devices):
1906
    """Detach the drbd device from local storage.
1907

1908
    """
1909
    if self.minor is None:
1910
      raise errors.BlockDeviceError("Can't attach to drbd8 during"
1911
                                    " RemoveChildren")
1912
    # early return if we don't actually have backing storage
1913
    info = self._GetDevInfo(self.minor)
1914
    if "local_dev" not in info:
1915
      return
1916
    if len(self._children) != 2:
1917
      raise errors.BlockDeviceError("We don't have two children: %s" %
1918
                                    self._children)
1919
    if self._children.count(None) == 2: # we don't actually have children :)
1920
      logger.Error("Requested detach while detached")
1921
      return
1922
    if len(devices) != 2:
1923
      raise errors.BlockDeviceError("We need two children in RemoveChildren")
1924
    for child, dev in zip(self._children, devices):
1925
      if dev != child.dev_path:
1926
        raise errors.BlockDeviceError("Mismatch in local storage"
1927
                                      " (%s != %s) in RemoveChildren" %
1928
                                      (dev, child.dev_path))
1929

    
1930
    if not self._ShutdownLocal(self.minor):
1931
      raise errors.BlockDeviceError("Can't detach from local storage")
1932
    self._children = []
1933

    
1934
  def SetSyncSpeed(self, kbytes):
1935
    """Set the speed of the DRBD syncer.
1936

1937
    """
1938
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1939
    if self.minor is None:
1940
      logger.Info("Instance not attached to a device")
1941
      return False
1942
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1943
                           kbytes])
1944
    if result.failed:
1945
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1946
    return not result.failed and children_result
1947

    
1948
  def GetSyncStatus(self):
1949
    """Returns the sync status of the device.
1950

1951
    Returns:
1952
     (sync_percent, estimated_time, is_degraded)
1953

1954
    If sync_percent is None, it means all is ok
1955
    If estimated_time is None, it means we can't esimate
1956
    the time needed, otherwise it's the time left in seconds
1957

1958
    """
1959
    if self.minor is None and not self.Attach():
1960
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1961
    proc_info = self._MassageProcData(self._GetProcData())
1962
    if self.minor not in proc_info:
1963
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1964
                                    self.minor)
1965
    line = proc_info[self.minor]
1966
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1967
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1968
    if match:
1969
      sync_percent = float(match.group(1))
1970
      hours = int(match.group(2))
1971
      minutes = int(match.group(3))
1972
      seconds = int(match.group(4))
1973
      est_time = hours * 3600 + minutes * 60 + seconds
1974
    else:
1975
      sync_percent = None
1976
      est_time = None
1977
    match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1978
    if not match:
1979
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1980
                                    self.minor)
1981
    client_state = match.group(1)
1982
    local_disk_state = match.group(2)
1983
    is_degraded = (client_state != "Connected" or
1984
                   local_disk_state != "UpToDate")
1985
    return sync_percent, est_time, is_degraded
1986

    
1987
  def GetStatus(self):
1988
    """Compute the status of the DRBD device
1989

1990
    Note that DRBD devices don't have the STATUS_EXISTING state.
1991

1992
    """
1993
    if self.minor is None and not self.Attach():
1994
      return self.STATUS_UNKNOWN
1995

    
1996
    data = self._GetProcData()
1997
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1998
                       self.minor)
1999
    for line in data:
2000
      mresult = match.match(line)
2001
      if mresult:
2002
        break
2003
    else:
2004
      logger.Error("Can't find myself!")
2005
      return self.STATUS_UNKNOWN
2006

    
2007
    state = mresult.group(2)
2008
    if state == "Primary":
2009
      result = self.STATUS_ONLINE
2010
    else:
2011
      result = self.STATUS_STANDBY
2012

    
2013
    return result
2014

    
2015
  def Open(self, force=False):
2016
    """Make the local state primary.
2017

2018
    If the 'force' parameter is given, the '--do-what-I-say' parameter
2019
    is given. Since this is a pottentialy dangerous operation, the
2020
    force flag should be only given after creation, when it actually
2021
    has to be given.
2022

2023
    """
2024
    if self.minor is None and not self.Attach():
2025
      logger.Error("DRBD cannot attach to a device during open")
2026
      return False
2027
    cmd = ["drbdsetup", self.dev_path, "primary"]
2028
    if force:
2029
      cmd.append("-o")
2030
    result = utils.RunCmd(cmd)
2031
    if result.failed:
2032
      logger.Error("Can't make drbd device primary: %s" % result.output)
2033
      return False
2034
    return True
2035

    
2036
  def Close(self):
2037
    """Make the local state secondary.
2038

2039
    This will, of course, fail if the device is in use.
2040

2041
    """
2042
    if self.minor is None and not self.Attach():
2043
      logger.Info("Instance not attached to a device")
2044
      raise errors.BlockDeviceError("Can't find device")
2045
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2046
    if result.failed:
2047
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
2048
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
2049

    
2050
  def Attach(self):
2051
    """Find a DRBD device which matches our config and attach to it.
2052

2053
    In case of partially attached (local device matches but no network
2054
    setup), we perform the network attach. If successful, we re-test
2055
    the attach if can return success.
2056

2057
    """
2058
    for minor in self._GetUsedDevs():
2059
      info = self._GetDevInfo(minor)
2060
      match_l = self._MatchesLocal(info)
2061
      match_r = self._MatchesNet(info)
2062
      if match_l and match_r:
2063
        break
2064
      if match_l and not match_r and "local_addr" not in info:
2065
        res_r = self._AssembleNet(minor,
2066
                                  (self._lhost, self._lport,
2067
                                   self._rhost, self._rport),
2068
                                  "C")
2069
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
2070
          break
2071
      # the weakest case: we find something that is only net attached
2072
      # even though we were passed some children at init time
2073
      if match_r and "local_dev" not in info:
2074
        break
2075
    else:
2076
      minor = None
2077

    
2078
    self._SetFromMinor(minor)
2079
    return minor is not None
2080

    
2081
  def Assemble(self):
2082
    """Assemble the drbd.
2083

2084
    Method:
2085
      - if we have a local backing device, we bind to it by:
2086
        - checking the list of used drbd devices
2087
        - check if the local minor use of any of them is our own device
2088
        - if yes, abort?
2089
        - if not, bind
2090
      - if we have a local/remote net info:
2091
        - redo the local backing device step for the remote device
2092
        - check if any drbd device is using the local port,
2093
          if yes abort
2094
        - check if any remote drbd device is using the remote
2095
          port, if yes abort (for now)
2096
        - bind our net port
2097
        - bind the remote net port
2098

2099
    """
2100
    self.Attach()
2101
    if self.minor is not None:
2102
      logger.Info("Already assembled")
2103
      return True
2104

    
2105
    result = super(DRBD8, self).Assemble()
2106
    if not result:
2107
      return result
2108

    
2109
    minor = self._FindUnusedMinor()
2110
    need_localdev_teardown = False
2111
    if self._children and self._children[0] and self._children[1]:
2112
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2113
                                   self._children[1].dev_path)
2114
      if not result:
2115
        return False
2116
      need_localdev_teardown = True
2117
    if self._lhost and self._lport and self._rhost and self._rport:
2118
      result = self._AssembleNet(minor,
2119
                                 (self._lhost, self._lport,
2120
                                  self._rhost, self._rport),
2121
                                 "C")
2122
      if not result:
2123
        if need_localdev_teardown:
2124
          # we will ignore failures from this
2125
          logger.Error("net setup failed, tearing down local device")
2126
          self._ShutdownAll(minor)
2127
        return False
2128
    self._SetFromMinor(minor)
2129
    return True
2130

    
2131
  @classmethod
2132
  def _ShutdownLocal(cls, minor):
2133
    """Detach from the local device.
2134

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

2138
    """
2139
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2140
    if result.failed:
2141
      logger.Error("Can't detach local device: %s" % result.output)
2142
    return not result.failed
2143

    
2144
  @classmethod
2145
  def _ShutdownNet(cls, minor):
2146
    """Disconnect from the remote peer.
2147

2148
    This fails if we don't have a local device.
2149

2150
    """
2151
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2152
    logger.Error("Can't shutdown network: %s" % result.output)
2153
    return not result.failed
2154

    
2155
  @classmethod
2156
  def _ShutdownAll(cls, minor):
2157
    """Deactivate the device.
2158

2159
    This will, of course, fail if the device is in use.
2160

2161
    """
2162
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2163
    if result.failed:
2164
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2165
    return not result.failed
2166

    
2167
  def Shutdown(self):
2168
    """Shutdown the DRBD device.
2169

2170
    """
2171
    if self.minor is None and not self.Attach():
2172
      logger.Info("DRBD device not attached to a device during Shutdown")
2173
      return True
2174
    if not self._ShutdownAll(self.minor):
2175
      return False
2176
    self.minor = None
2177
    self.dev_path = None
2178
    return True
2179

    
2180
  def Rename(self, new_uid):
2181
    """Re-connect this device to another peer.
2182

2183
    """
2184
    if self.minor is None:
2185
      raise errors.BlockDeviceError("Device not attached during rename")
2186
    if self._rhost is not None:
2187
      # this means we did have a host when we attached, so we are connected
2188
      if not self._ShutdownNet(self.minor):
2189
        raise errors.BlockDeviceError("Can't disconnect from remote peer")
2190
      old_id = self.unique_id
2191
    else:
2192
      old_id = None
2193
    self.unique_id = new_uid
2194
    if not self._AssembleNet(self.minor, self.unique_id, "C"):
2195
      logger.Error("Can't attach to new peer!")
2196
      if old_id is not None:
2197
        self._AssembleNet(self.minor, old_id, "C")
2198
      self.unique_id = old_id
2199
      raise errors.BlockDeviceError("Can't attach to new peer")
2200

    
2201
  def Remove(self):
2202
    """Stub remove for DRBD devices.
2203

2204
    """
2205
    return self.Shutdown()
2206

    
2207
  @classmethod
2208
  def Create(cls, unique_id, children, size):
2209
    """Create a new DRBD8 device.
2210

2211
    Since DRBD devices are not created per se, just assembled, this
2212
    function only initializes the metadata.
2213

2214
    """
2215
    if len(children) != 2:
2216
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2217
    meta = children[1]
2218
    meta.Assemble()
2219
    if not meta.Attach():
2220
      raise errors.BlockDeviceError("Can't attach to meta device")
2221
    if not cls._CheckMetaSize(meta.dev_path):
2222
      raise errors.BlockDeviceError("Invalid meta device size")
2223
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2224
    if not cls._IsValidMeta(meta.dev_path):
2225
      raise errors.BlockDeviceError("Cannot initalize meta device")
2226
    return cls(unique_id, children)
2227

    
2228

    
2229
DEV_MAP = {
2230
  constants.LD_LV: LogicalVolume,
2231
  constants.LD_MD_R1: MDRaid1,
2232
  constants.LD_DRBD7: DRBDev,
2233
  constants.LD_DRBD8: DRBD8,
2234
  }
2235

    
2236

    
2237
def FindDevice(dev_type, unique_id, children):
2238
  """Search for an existing, assembled device.
2239

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

2243
  """
2244
  if dev_type not in DEV_MAP:
2245
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2246
  device = DEV_MAP[dev_type](unique_id, children)
2247
  if not device.Attach():
2248
    return None
2249
  return  device
2250

    
2251

    
2252
def AttachOrAssemble(dev_type, unique_id, children):
2253
  """Try to attach or assemble an existing device.
2254

2255
  This will attach to an existing assembled device or will assemble
2256
  the device, as needed, to bring it fully up.
2257

2258
  """
2259
  if dev_type not in DEV_MAP:
2260
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2261
  device = DEV_MAP[dev_type](unique_id, children)
2262
  if not device.Attach():
2263
    device.Assemble()
2264
  if not device.Attach():
2265
    raise errors.BlockDeviceError("Can't find a valid block device for"
2266
                                  " %s/%s/%s" %
2267
                                  (dev_type, unique_id, children))
2268
  return device
2269

    
2270

    
2271
def Create(dev_type, unique_id, children, size):
2272
  """Create a device.
2273

2274
  """
2275
  if dev_type not in DEV_MAP:
2276
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2277
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2278
  return device