Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ fcb1f331

History | View | Annotate | Download (71.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
    This is a no-op for the LV device type. Eventually, we could
419
    lvchange -ay here if we see that the LV is not active.
420

421
    """
422
    return True
423

    
424
  def Shutdown(self):
425
    """Shutdown the device.
426

427
    This is a no-op for the LV device type, as we don't deactivate the
428
    volumes on shutdown.
429

430
    """
431
    return True
432

    
433
  def GetStatus(self):
434
    """Return the status of the device.
435

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

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

    
459
    return retval
460

    
461
  def GetSyncStatus(self):
462
    """Returns the sync status of the device.
463

464
    If this device is a mirroring device, this function returns the
465
    status of the mirror.
466

467
    Returns:
468
     (sync_percent, estimated_time, is_degraded, ldisk)
469

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

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

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

    
495
  def Open(self, force=False):
496
    """Make the device ready for I/O.
497

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

500
    """
501
    return True
502

    
503
  def Close(self):
504
    """Notifies that the device will no longer be used for I/O.
505

506
    This is a no-op for the LV device type.
507

508
    """
509
    return True
510

    
511
  def Snapshot(self, size):
512
    """Create a snapshot copy of an lvm block device.
513

514
    """
515
    snap_name = self._lv_name + ".snap"
516

    
517
    # remove existing snapshot if found
518
    snap = LogicalVolume((self._vg_name, snap_name), None)
519
    snap.Remove()
520

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

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

    
538
    return snap_name
539

    
540
  def SetInfo(self, text):
541
    """Update metadata with info text.
542

543
    """
544
    BlockDev.SetInfo(self, text)
545

    
546
    # Replace invalid characters
547
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
548
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
549

    
550
    # Only up to 128 characters are allowed
551
    text = text[:128]
552

    
553
    result = utils.RunCmd(["lvchange", "--addtag", text,
554
                           self.dev_path])
555
    if result.failed:
556
      raise errors.BlockDeviceError("Command: %s error: %s" %
557
                                    (result.cmd, result.fail_reason))
558

    
559

    
560
class MDRaid1(BlockDev):
561
  """raid1 device implemented via md.
562

563
  """
564
  def __init__(self, unique_id, children):
565
    super(MDRaid1, self).__init__(unique_id, children)
566
    self.major = 9
567
    self.Attach()
568

    
569
  def Attach(self):
570
    """Find an array which matches our config and attach to it.
571

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

574
    """
575
    minor = self._FindMDByUUID(self.unique_id)
576
    if minor is not None:
577
      self._SetFromMinor(minor)
578
    else:
579
      self.minor = None
580
      self.dev_path = None
581

    
582
    return (minor is not None)
583

    
584
  @staticmethod
585
  def _GetUsedDevs():
586
    """Compute the list of in-use MD devices.
587

588
    It doesn't matter if the used device have other raid level, just
589
    that they are in use.
590

591
    """
592
    mdstat = open("/proc/mdstat", "r")
593
    data = mdstat.readlines()
594
    mdstat.close()
595

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

    
604
    return used_md
605

    
606
  @staticmethod
607
  def _GetDevInfo(minor):
608
    """Get info about a MD device.
609

610
    Currently only uuid is returned.
611

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

    
628
  @staticmethod
629
  def _FindUnusedMinor():
630
    """Compute an unused MD minor.
631

632
    This code assumes that there are 256 minors only.
633

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

    
646
  @classmethod
647
  def _FindMDByUUID(cls, uuid):
648
    """Find the minor of an MD array with a given UUID.
649

650
    """
651
    md_list = cls._GetUsedDevs()
652
    for minor in md_list:
653
      info = cls._GetDevInfo(minor)
654
      if info and info["uuid"] == uuid:
655
        return minor
656
    return None
657

    
658
  @staticmethod
659
  def _ZeroSuperblock(dev_path):
660
    """Zero the possible locations for an MD superblock.
661

662
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
663
    fails in versions 2.x with the same error code as non-writable
664
    device.
665

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

673
    To cover all situations, the zero-ing will be:
674
      - 0k to 128k
675
      - -128k to end
676

677
    As such, the minimum device size must be 128k, otherwise we'll get
678
    I/O errors.
679

680
    Note that this function depends on the fact that one can open,
681
    read and write block devices normally.
682

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

    
701
  @classmethod
702
  def Create(cls, unique_id, children, size):
703
    """Create a new MD raid1 array.
704

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

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

    
735
  def Remove(self):
736
    """Stub remove function for MD RAID 1 arrays.
737

738
    We don't remove the superblock right now. Mark a to do.
739

740
    """
741
    #TODO: maybe zero superblock on child devices?
742
    return self.Shutdown()
743

    
744
  def Rename(self, new_id):
745
    """Rename a device.
746

747
    This is not supported for md raid1 devices.
748

749
    """
750
    raise errors.ProgrammerError("Can't rename a md raid1 device")
751

    
752
  def AddChildren(self, devices):
753
    """Add new member(s) to the md raid1.
754

755
    """
756
    if self.minor is None and not self.Attach():
757
      raise errors.BlockDeviceError("Can't attach to device")
758

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

    
776
  def RemoveChildren(self, devices):
777
    """Remove member(s) from the md raid1.
778

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

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

    
817
  def GetStatus(self):
818
    """Return the status of the device.
819

820
    """
821
    self.Attach()
822
    if self.minor is None:
823
      retval = self.STATUS_UNKNOWN
824
    else:
825
      retval = self.STATUS_ONLINE
826
    return retval
827

    
828
  def _SetFromMinor(self, minor):
829
    """Set our parameters based on the given minor.
830

831
    This sets our minor variable and our dev_path.
832

833
    """
834
    self.minor = minor
835
    self.dev_path = "/dev/md%d" % minor
836

    
837
  def Assemble(self):
838
    """Assemble the MD device.
839

840
    At this point we should have:
841
      - list of children devices
842
      - uuid
843

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

    
867
  def Shutdown(self):
868
    """Tear down the MD array.
869

870
    This does a 'mdadm --stop' so after this command, the array is no
871
    longer available.
872

873
    """
874
    if self.minor is None and not self.Attach():
875
      logger.Info("MD object not attached to a device")
876
      return True
877

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

    
886
  def SetSyncSpeed(self, kbytes):
887
    """Set the maximum sync speed for the MD array.
888

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

    
906
  def GetSyncStatus(self):
907
    """Returns the sync status of the device.
908

909
    Returns:
910
     (sync_percent, estimated_time, is_degraded, ldisk)
911

912
    If sync_percent is None, it means all is ok
913
    If estimated_time is None, it means we can't esimate
914
    the time needed, otherwise it's the time left in seconds.
915

916
    The ldisk parameter is always true for MD devices.
917

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

    
946
  def Open(self, force=False):
947
    """Make the device ready for I/O.
948

949
    This is a no-op for the MDRaid1 device type, although we could use
950
    the 2.6.18's new array_state thing.
951

952
    """
953
    return True
954

    
955
  def Close(self):
956
    """Notifies that the device will no longer be used for I/O.
957

958
    This is a no-op for the MDRaid1 device type, but see comment for
959
    `Open()`.
960

961
    """
962
    return True
963

    
964

    
965
class BaseDRBD(BlockDev):
966
  """Base DRBD class.
967

968
  This class contains a few bits of common functionality between the
969
  0.7 and 8.x versions of DRBD.
970

971
  """
972
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
973
                           r" \(api:(\d+)/proto:(\d+)\)")
974
  _DRBD_MAJOR = 147
975
  _ST_UNCONFIGURED = "Unconfigured"
976
  _ST_WFCONNECTION = "WFConnection"
977
  _ST_CONNECTED = "Connected"
978

    
979
  @staticmethod
980
  def _GetProcData():
981
    """Return data from /proc/drbd.
982

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

    
993
  @staticmethod
994
  def _MassageProcData(data):
995
    """Transform the output of _GetProdData into a nicer form.
996

997
    Returns:
998
      a dictionary of minor: joined lines from /proc/drbd for that minor
999

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

    
1019
  @classmethod
1020
  def _GetVersion(cls):
1021
    """Return the DRBD version.
1022

1023
    This will return a list [k_major, k_minor, k_point, api, proto].
1024

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

    
1034
  @staticmethod
1035
  def _DevPath(minor):
1036
    """Return the path to a drbd device for a given minor.
1037

1038
    """
1039
    return "/dev/drbd%d" % minor
1040

    
1041
  @classmethod
1042
  def _GetUsedDevs(cls):
1043
    """Compute the list of used DRBD devices.
1044

1045
    """
1046
    data = cls._GetProcData()
1047

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

    
1060
    return used_devs
1061

    
1062
  def _SetFromMinor(self, minor):
1063
    """Set our parameters based on the given minor.
1064

1065
    This sets our minor variable and our dev_path.
1066

1067
    """
1068
    if minor is None:
1069
      self.minor = self.dev_path = None
1070
    else:
1071
      self.minor = minor
1072
      self.dev_path = self._DevPath(minor)
1073

    
1074
  @staticmethod
1075
  def _CheckMetaSize(meta_device):
1076
    """Check if the given meta device looks like a valid one.
1077

1078
    This currently only check the size, which must be around
1079
    128MiB.
1080

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

    
1100
  def Rename(self, new_id):
1101
    """Rename a device.
1102

1103
    This is not supported for drbd devices.
1104

1105
    """
1106
    raise errors.ProgrammerError("Can't rename a drbd device")
1107

    
1108

    
1109
class DRBDev(BaseDRBD):
1110
  """DRBD block device.
1111

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

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

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

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

    
1138
  @classmethod
1139
  def _FindUnusedMinor(cls):
1140
    """Find an unused DRBD device.
1141

1142
    """
1143
    data = cls._GetProcData()
1144

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

    
1153
  @classmethod
1154
  def _GetDevInfo(cls, minor):
1155
    """Get details about a given DRBD minor.
1156

1157
    This return, if available, the local backing device in (major,
1158
    minor) formant and the local and remote (ip, port) information.
1159

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

    
1204
  def _MatchesLocal(self, info):
1205
    """Test if our local config matches with an existing device.
1206

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

1212
    """
1213
    if not ("local_dev" in info and "meta_dev" in info and
1214
            "meta_index" in info):
1215
      return False
1216

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

    
1231
  def _MatchesNet(self, info):
1232
    """Test if our network config matches with an existing device.
1233

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

1239
    """
1240
    if (((self._lhost is None and not ("local_addr" in info)) and
1241
         (self._rhost is None and not ("remote_addr" in info)))):
1242
      return True
1243

    
1244
    if self._lhost is None:
1245
      return False
1246

    
1247
    if not ("local_addr" in info and
1248
            "remote_addr" in info):
1249
      return False
1250

    
1251
    retval = (info["local_addr"] == (self._lhost, self._lport))
1252
    retval = (retval and
1253
              info["remote_addr"] == (self._rhost, self._rport))
1254
    return retval
1255

    
1256
  @classmethod
1257
  def _AssembleLocal(cls, minor, backend, meta):
1258
    """Configure the local part of a DRBD device.
1259

1260
    This is the first thing that must be done on an unconfigured DRBD
1261
    device. And it must be done only once.
1262

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

    
1272
  @classmethod
1273
  def _ShutdownLocal(cls, minor):
1274
    """Detach from the local device.
1275

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

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

    
1285
  @staticmethod
1286
  def _ShutdownAll(minor):
1287
    """Deactivate the device.
1288

1289
    This will, of course, fail if the device is in use.
1290

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

    
1297
  @classmethod
1298
  def _AssembleNet(cls, minor, net_info, protocol):
1299
    """Configure the network part of the device.
1300

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

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

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

    
1336
  @classmethod
1337
  def _ShutdownNet(cls, minor):
1338
    """Disconnect from the remote peer.
1339

1340
    This fails if we don't have a local device.
1341

1342
    """
1343
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1344
    logger.Error("Can't shutdown network: %s" % result.output)
1345
    return not result.failed
1346

    
1347
  def Assemble(self):
1348
    """Assemble the drbd.
1349

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

1365
    """
1366
    self.Attach()
1367
    if self.minor is not None:
1368
      logger.Info("Already assembled")
1369
      return True
1370

    
1371
    result = super(DRBDev, self).Assemble()
1372
    if not result:
1373
      return result
1374

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

    
1397
  def Shutdown(self):
1398
    """Shutdown the DRBD device.
1399

1400
    """
1401
    if self.minor is None and not self.Attach():
1402
      logger.Info("DRBD device not attached to a device during Shutdown")
1403
      return True
1404
    if not self._ShutdownAll(self.minor):
1405
      return False
1406
    self.minor = None
1407
    self.dev_path = None
1408
    return True
1409

    
1410
  def Attach(self):
1411
    """Find a DRBD device which matches our config and attach to it.
1412

1413
    In case of partially attached (local device matches but no network
1414
    setup), we perform the network attach. If successful, we re-test
1415
    the attach if can return success.
1416

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

    
1434
    self._SetFromMinor(minor)
1435
    return minor is not None
1436

    
1437
  def Open(self, force=False):
1438
    """Make the local state primary.
1439

1440
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1441
    is given. Since this is a pottentialy dangerous operation, the
1442
    force flag should be only given after creation, when it actually
1443
    has to be given.
1444

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

    
1458
  def Close(self):
1459
    """Make the local state secondary.
1460

1461
    This will, of course, fail if the device is in use.
1462

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

    
1472
  def SetSyncSpeed(self, kbytes):
1473
    """Set the speed of the DRBD syncer.
1474

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

    
1486
  def GetSyncStatus(self):
1487
    """Returns the sync status of the device.
1488

1489
    Returns:
1490
     (sync_percent, estimated_time, is_degraded, ldisk)
1491

1492
    If sync_percent is None, it means all is ok
1493
    If estimated_time is None, it means we can't esimate
1494
    the time needed, otherwise it's the time left in seconds.
1495

1496
    The ldisk parameter will be returned as True, since the DRBD7
1497
    devices have not been converted.
1498

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

    
1526
  def GetStatus(self):
1527
    """Compute the status of the DRBD device
1528

1529
    Note that DRBD devices don't have the STATUS_EXISTING state.
1530

1531
    """
1532
    if self.minor is None and not self.Attach():
1533
      return self.STATUS_UNKNOWN
1534

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

    
1546
    state = mresult.group(2)
1547
    if state == "Primary":
1548
      result = self.STATUS_ONLINE
1549
    else:
1550
      result = self.STATUS_STANDBY
1551

    
1552
    return result
1553

    
1554
  @staticmethod
1555
  def _ZeroDevice(device):
1556
    """Zero a device.
1557

1558
    This writes until we get ENOSPC.
1559

1560
    """
1561
    f = open(device, "w")
1562
    buf = "\0" * 1048576
1563
    try:
1564
      while True:
1565
        f.write(buf)
1566
    except IOError, err:
1567
      if err.errno != errno.ENOSPC:
1568
        raise
1569

    
1570
  @classmethod
1571
  def Create(cls, unique_id, children, size):
1572
    """Create a new DRBD device.
1573

1574
    Since DRBD devices are not created per se, just assembled, this
1575
    function just zeroes the meta device.
1576

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

    
1591
  def Remove(self):
1592
    """Stub remove for DRBD devices.
1593

1594
    """
1595
    return self.Shutdown()
1596

    
1597

    
1598
class DRBD8(BaseDRBD):
1599
  """DRBD v8.x block device.
1600

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

1605
  The unique_id for the drbd device is the (local_ip, local_port,
1606
  remote_ip, remote_port) tuple, and it must have two children: the
1607
  data device and the meta_device. The meta device is checked for
1608
  valid size and is zeroed on create.
1609

1610
  """
1611
  _MAX_MINORS = 255
1612
  _PARSE_SHOW = None
1613

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

    
1625
    if len(children) not in (0, 2):
1626
      raise ValueError("Invalid configuration data %s" % str(children))
1627
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1628
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1629
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1630
    self.Attach()
1631

    
1632
  @classmethod
1633
  def _InitMeta(cls, minor, dev_path):
1634
    """Initialize a meta device.
1635

1636
    This will not work if the given minor is in use.
1637

1638
    """
1639
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1640
                           "v08", dev_path, "0", "create-md"])
1641
    if result.failed:
1642
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1643
                                    result.output)
1644

    
1645
  @classmethod
1646
  def _FindUnusedMinor(cls):
1647
    """Find an unused DRBD device.
1648

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

1652
    """
1653
    data = cls._GetProcData()
1654

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

    
1673
  @classmethod
1674
  def _IsValidMeta(cls, meta_device):
1675
    """Check if the given meta device looks like a valid one.
1676

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

    
1688
  @classmethod
1689
  def _GetShowParser(cls):
1690
    """Return a parser for `drbd show` output.
1691

1692
    This will either create or return an already-create parser for the
1693
    output of the command `drbd show`.
1694

1695
    """
1696
    if cls._PARSE_SHOW is not None:
1697
      return cls._PARSE_SHOW
1698

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

    
1706
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1707
    defa = pyp.Literal("_is_default").suppress()
1708
    dbl_quote = pyp.Literal('"').suppress()
1709

    
1710
    keyword = pyp.Word(pyp.alphanums + '-')
1711

    
1712
    # value types
1713
    value = pyp.Word(pyp.alphanums + '_-/.:')
1714
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1715
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1716
                 number)
1717
    # meta device, extended syntax
1718
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1719
                  number + pyp.Word(']').suppress())
1720

    
1721
    # a statement
1722
    stmt = (~rbrace + keyword + ~lbrace +
1723
            (addr_port ^ value ^ quoted ^ meta_value) +
1724
            pyp.Optional(defa) + semi +
1725
            pyp.Optional(pyp.restOfLine).suppress())
1726

    
1727
    # an entire section
1728
    section_name = pyp.Word(pyp.alphas + '_')
1729
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1730

    
1731
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1732
    bnf.ignore(comment)
1733

    
1734
    cls._PARSE_SHOW = bnf
1735

    
1736
    return bnf
1737

    
1738
  @classmethod
1739
  def _GetDevInfo(cls, minor):
1740
    """Get details about a given DRBD minor.
1741

1742
    This return, if available, the local backing device (as a path)
1743
    and the local and remote (ip, port) information.
1744

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

    
1755
    bnf = cls._GetShowParser()
1756
    # run pyparse
1757

    
1758
    try:
1759
      results = bnf.parseString(out)
1760
    except pyp.ParseException, err:
1761
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1762
                                    str(err))
1763

    
1764
    # and massage the results into our desired format
1765
    for section in results:
1766
      sname = section[0]
1767
      if sname == "_this_host":
1768
        for lst in section[1:]:
1769
          if lst[0] == "disk":
1770
            data["local_dev"] = lst[1]
1771
          elif lst[0] == "meta-disk":
1772
            data["meta_dev"] = lst[1]
1773
            data["meta_index"] = lst[2]
1774
          elif lst[0] == "address":
1775
            data["local_addr"] = tuple(lst[1:])
1776
      elif sname == "_remote_host":
1777
        for lst in section[1:]:
1778
          if lst[0] == "address":
1779
            data["remote_addr"] = tuple(lst[1:])
1780
    return data
1781

    
1782
  def _MatchesLocal(self, info):
1783
    """Test if our local config matches with an existing device.
1784

1785
    The parameter should be as returned from `_GetDevInfo()`. This
1786
    method tests if our local backing device is the same as the one in
1787
    the info parameter, in effect testing if we look like the given
1788
    device.
1789

1790
    """
1791
    if self._children:
1792
      backend, meta = self._children
1793
    else:
1794
      backend = meta = None
1795

    
1796
    if backend is not None:
1797
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1798
    else:
1799
      retval = ("local_dev" not in info)
1800

    
1801
    if meta is not None:
1802
      retval = retval and ("meta_dev" in info and
1803
                           info["meta_dev"] == meta.dev_path)
1804
      retval = retval and ("meta_index" in info and
1805
                           info["meta_index"] == 0)
1806
    else:
1807
      retval = retval and ("meta_dev" not in info and
1808
                           "meta_index" not in info)
1809
    return retval
1810

    
1811
  def _MatchesNet(self, info):
1812
    """Test if our network config matches with an existing device.
1813

1814
    The parameter should be as returned from `_GetDevInfo()`. This
1815
    method tests if our network configuration is the same as the one
1816
    in the info parameter, in effect testing if we look like the given
1817
    device.
1818

1819
    """
1820
    if (((self._lhost is None and not ("local_addr" in info)) and
1821
         (self._rhost is None and not ("remote_addr" in info)))):
1822
      return True
1823

    
1824
    if self._lhost is None:
1825
      return False
1826

    
1827
    if not ("local_addr" in info and
1828
            "remote_addr" in info):
1829
      return False
1830

    
1831
    retval = (info["local_addr"] == (self._lhost, self._lport))
1832
    retval = (retval and
1833
              info["remote_addr"] == (self._rhost, self._rport))
1834
    return retval
1835

    
1836
  @classmethod
1837
  def _AssembleLocal(cls, minor, backend, meta):
1838
    """Configure the local part of a DRBD device.
1839

1840
    This is the first thing that must be done on an unconfigured DRBD
1841
    device. And it must be done only once.
1842

1843
    """
1844
    if not cls._IsValidMeta(meta):
1845
      return False
1846
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1847
                           backend, meta, "0", "-e", "detach",
1848
                           "--create-device"])
1849
    if result.failed:
1850
      logger.Error("Can't attach local disk: %s" % result.output)
1851
    return not result.failed
1852

    
1853
  @classmethod
1854
  def _AssembleNet(cls, minor, net_info, protocol,
1855
                   dual_pri=False, hmac=None, secret=None):
1856
    """Configure the network part of the device.
1857

1858
    """
1859
    lhost, lport, rhost, rport = net_info
1860
    args = ["drbdsetup", cls._DevPath(minor), "net",
1861
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1862
            "-A", "discard-zero-changes",
1863
            "-B", "consensus",
1864
            ]
1865
    if dual_pri:
1866
      args.append("-m")
1867
    if hmac and secret:
1868
      args.extend(["-a", hmac, "-x", secret])
1869
    result = utils.RunCmd(args)
1870
    if result.failed:
1871
      logger.Error("Can't setup network for dbrd device: %s" %
1872
                   result.fail_reason)
1873
      return False
1874

    
1875
    timeout = time.time() + 10
1876
    ok = False
1877
    while time.time() < timeout:
1878
      info = cls._GetDevInfo(minor)
1879
      if not "local_addr" in info or not "remote_addr" in info:
1880
        time.sleep(1)
1881
        continue
1882
      if (info["local_addr"] != (lhost, lport) or
1883
          info["remote_addr"] != (rhost, rport)):
1884
        time.sleep(1)
1885
        continue
1886
      ok = True
1887
      break
1888
    if not ok:
1889
      logger.Error("Timeout while configuring network")
1890
      return False
1891
    return True
1892

    
1893
  def AddChildren(self, devices):
1894
    """Add a disk to the DRBD device.
1895

1896
    """
1897
    if self.minor is None:
1898
      raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1899
    if len(devices) != 2:
1900
      raise errors.BlockDeviceError("Need two devices for AddChildren")
1901
    info = self._GetDevInfo(self.minor)
1902
    if "local_dev" in info:
1903
      raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1904
    backend, meta = devices
1905
    if backend.dev_path is None or meta.dev_path is None:
1906
      raise errors.BlockDeviceError("Children not ready during AddChildren")
1907
    backend.Open()
1908
    meta.Open()
1909
    if not self._CheckMetaSize(meta.dev_path):
1910
      raise errors.BlockDeviceError("Invalid meta device size")
1911
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1912
    if not self._IsValidMeta(meta.dev_path):
1913
      raise errors.BlockDeviceError("Cannot initalize meta device")
1914

    
1915
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1916
      raise errors.BlockDeviceError("Can't attach to local storage")
1917
    self._children = devices
1918

    
1919
  def RemoveChildren(self, devices):
1920
    """Detach the drbd device from local storage.
1921

1922
    """
1923
    if self.minor is None:
1924
      raise errors.BlockDeviceError("Can't attach to drbd8 during"
1925
                                    " RemoveChildren")
1926
    # early return if we don't actually have backing storage
1927
    info = self._GetDevInfo(self.minor)
1928
    if "local_dev" not in info:
1929
      return
1930
    if len(self._children) != 2:
1931
      raise errors.BlockDeviceError("We don't have two children: %s" %
1932
                                    self._children)
1933
    if self._children.count(None) == 2: # we don't actually have children :)
1934
      logger.Error("Requested detach while detached")
1935
      return
1936
    if len(devices) != 2:
1937
      raise errors.BlockDeviceError("We need two children in RemoveChildren")
1938
    for child, dev in zip(self._children, devices):
1939
      if dev != child.dev_path:
1940
        raise errors.BlockDeviceError("Mismatch in local storage"
1941
                                      " (%s != %s) in RemoveChildren" %
1942
                                      (dev, child.dev_path))
1943

    
1944
    if not self._ShutdownLocal(self.minor):
1945
      raise errors.BlockDeviceError("Can't detach from local storage")
1946
    self._children = []
1947

    
1948
  def SetSyncSpeed(self, kbytes):
1949
    """Set the speed of the DRBD syncer.
1950

1951
    """
1952
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1953
    if self.minor is None:
1954
      logger.Info("Instance not attached to a device")
1955
      return False
1956
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1957
                           kbytes])
1958
    if result.failed:
1959
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1960
    return not result.failed and children_result
1961

    
1962
  def GetSyncStatus(self):
1963
    """Returns the sync status of the device.
1964

1965
    Returns:
1966
     (sync_percent, estimated_time, is_degraded)
1967

1968
    If sync_percent is None, it means all is ok
1969
    If estimated_time is None, it means we can't esimate
1970
    the time needed, otherwise it's the time left in seconds.
1971

1972

1973
    We set the is_degraded parameter to True on two conditions:
1974
    network not connected or local disk missing.
1975

1976
    We compute the ldisk parameter based on wheter we have a local
1977
    disk or not.
1978

1979
    """
1980
    if self.minor is None and not self.Attach():
1981
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1982
    proc_info = self._MassageProcData(self._GetProcData())
1983
    if self.minor not in proc_info:
1984
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1985
                                    self.minor)
1986
    line = proc_info[self.minor]
1987
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1988
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1989
    if match:
1990
      sync_percent = float(match.group(1))
1991
      hours = int(match.group(2))
1992
      minutes = int(match.group(3))
1993
      seconds = int(match.group(4))
1994
      est_time = hours * 3600 + minutes * 60 + seconds
1995
    else:
1996
      sync_percent = None
1997
      est_time = None
1998
    match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1999
    if not match:
2000
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
2001
                                    self.minor)
2002
    client_state = match.group(1)
2003
    local_disk_state = match.group(2)
2004
    ldisk = local_disk_state != "UpToDate"
2005
    is_degraded = client_state != "Connected"
2006
    return sync_percent, est_time, is_degraded or ldisk, ldisk
2007

    
2008
  def GetStatus(self):
2009
    """Compute the status of the DRBD device
2010

2011
    Note that DRBD devices don't have the STATUS_EXISTING state.
2012

2013
    """
2014
    if self.minor is None and not self.Attach():
2015
      return self.STATUS_UNKNOWN
2016

    
2017
    data = self._GetProcData()
2018
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
2019
                       self.minor)
2020
    for line in data:
2021
      mresult = match.match(line)
2022
      if mresult:
2023
        break
2024
    else:
2025
      logger.Error("Can't find myself!")
2026
      return self.STATUS_UNKNOWN
2027

    
2028
    state = mresult.group(2)
2029
    if state == "Primary":
2030
      result = self.STATUS_ONLINE
2031
    else:
2032
      result = self.STATUS_STANDBY
2033

    
2034
    return result
2035

    
2036
  def Open(self, force=False):
2037
    """Make the local state primary.
2038

2039
    If the 'force' parameter is given, the '--do-what-I-say' parameter
2040
    is given. Since this is a pottentialy dangerous operation, the
2041
    force flag should be only given after creation, when it actually
2042
    has to be given.
2043

2044
    """
2045
    if self.minor is None and not self.Attach():
2046
      logger.Error("DRBD cannot attach to a device during open")
2047
      return False
2048
    cmd = ["drbdsetup", self.dev_path, "primary"]
2049
    if force:
2050
      cmd.append("-o")
2051
    result = utils.RunCmd(cmd)
2052
    if result.failed:
2053
      logger.Error("Can't make drbd device primary: %s" % result.output)
2054
      return False
2055
    return True
2056

    
2057
  def Close(self):
2058
    """Make the local state secondary.
2059

2060
    This will, of course, fail if the device is in use.
2061

2062
    """
2063
    if self.minor is None and not self.Attach():
2064
      logger.Info("Instance not attached to a device")
2065
      raise errors.BlockDeviceError("Can't find device")
2066
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2067
    if result.failed:
2068
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
2069
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
2070

    
2071
  def Attach(self):
2072
    """Find a DRBD device which matches our config and attach to it.
2073

2074
    In case of partially attached (local device matches but no network
2075
    setup), we perform the network attach. If successful, we re-test
2076
    the attach if can return success.
2077

2078
    """
2079
    for minor in self._GetUsedDevs():
2080
      info = self._GetDevInfo(minor)
2081
      match_l = self._MatchesLocal(info)
2082
      match_r = self._MatchesNet(info)
2083
      if match_l and match_r:
2084
        break
2085
      if match_l and not match_r and "local_addr" not in info:
2086
        res_r = self._AssembleNet(minor,
2087
                                  (self._lhost, self._lport,
2088
                                   self._rhost, self._rport),
2089
                                  "C")
2090
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
2091
          break
2092
      # the weakest case: we find something that is only net attached
2093
      # even though we were passed some children at init time
2094
      if match_r and "local_dev" not in info:
2095
        break
2096
    else:
2097
      minor = None
2098

    
2099
    self._SetFromMinor(minor)
2100
    return minor is not None
2101

    
2102
  def Assemble(self):
2103
    """Assemble the drbd.
2104

2105
    Method:
2106
      - if we have a local backing device, we bind to it by:
2107
        - checking the list of used drbd devices
2108
        - check if the local minor use of any of them is our own device
2109
        - if yes, abort?
2110
        - if not, bind
2111
      - if we have a local/remote net info:
2112
        - redo the local backing device step for the remote device
2113
        - check if any drbd device is using the local port,
2114
          if yes abort
2115
        - check if any remote drbd device is using the remote
2116
          port, if yes abort (for now)
2117
        - bind our net port
2118
        - bind the remote net port
2119

2120
    """
2121
    self.Attach()
2122
    if self.minor is not None:
2123
      logger.Info("Already assembled")
2124
      return True
2125

    
2126
    result = super(DRBD8, self).Assemble()
2127
    if not result:
2128
      return result
2129

    
2130
    minor = self._FindUnusedMinor()
2131
    need_localdev_teardown = False
2132
    if self._children and self._children[0] and self._children[1]:
2133
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2134
                                   self._children[1].dev_path)
2135
      if not result:
2136
        return False
2137
      need_localdev_teardown = True
2138
    if self._lhost and self._lport and self._rhost and self._rport:
2139
      result = self._AssembleNet(minor,
2140
                                 (self._lhost, self._lport,
2141
                                  self._rhost, self._rport),
2142
                                 "C")
2143
      if not result:
2144
        if need_localdev_teardown:
2145
          # we will ignore failures from this
2146
          logger.Error("net setup failed, tearing down local device")
2147
          self._ShutdownAll(minor)
2148
        return False
2149
    self._SetFromMinor(minor)
2150
    return True
2151

    
2152
  @classmethod
2153
  def _ShutdownLocal(cls, minor):
2154
    """Detach from the local device.
2155

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

2159
    """
2160
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2161
    if result.failed:
2162
      logger.Error("Can't detach local device: %s" % result.output)
2163
    return not result.failed
2164

    
2165
  @classmethod
2166
  def _ShutdownNet(cls, minor):
2167
    """Disconnect from the remote peer.
2168

2169
    This fails if we don't have a local device.
2170

2171
    """
2172
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2173
    logger.Error("Can't shutdown network: %s" % result.output)
2174
    return not result.failed
2175

    
2176
  @classmethod
2177
  def _ShutdownAll(cls, minor):
2178
    """Deactivate the device.
2179

2180
    This will, of course, fail if the device is in use.
2181

2182
    """
2183
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2184
    if result.failed:
2185
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2186
    return not result.failed
2187

    
2188
  def Shutdown(self):
2189
    """Shutdown the DRBD device.
2190

2191
    """
2192
    if self.minor is None and not self.Attach():
2193
      logger.Info("DRBD device not attached to a device during Shutdown")
2194
      return True
2195
    if not self._ShutdownAll(self.minor):
2196
      return False
2197
    self.minor = None
2198
    self.dev_path = None
2199
    return True
2200

    
2201
  def Rename(self, new_uid):
2202
    """Re-connect this device to another peer.
2203

2204
    """
2205
    if self.minor is None:
2206
      raise errors.BlockDeviceError("Device not attached during rename")
2207
    if self._rhost is not None:
2208
      # this means we did have a host when we attached, so we are connected
2209
      if not self._ShutdownNet(self.minor):
2210
        raise errors.BlockDeviceError("Can't disconnect from remote peer")
2211
      old_id = self.unique_id
2212
    else:
2213
      old_id = None
2214
    self.unique_id = new_uid
2215
    if not self._AssembleNet(self.minor, self.unique_id, "C"):
2216
      logger.Error("Can't attach to new peer!")
2217
      if old_id is not None:
2218
        self._AssembleNet(self.minor, old_id, "C")
2219
      self.unique_id = old_id
2220
      raise errors.BlockDeviceError("Can't attach to new peer")
2221

    
2222
  def Remove(self):
2223
    """Stub remove for DRBD devices.
2224

2225
    """
2226
    return self.Shutdown()
2227

    
2228
  @classmethod
2229
  def Create(cls, unique_id, children, size):
2230
    """Create a new DRBD8 device.
2231

2232
    Since DRBD devices are not created per se, just assembled, this
2233
    function only initializes the metadata.
2234

2235
    """
2236
    if len(children) != 2:
2237
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2238
    meta = children[1]
2239
    meta.Assemble()
2240
    if not meta.Attach():
2241
      raise errors.BlockDeviceError("Can't attach to meta device")
2242
    if not cls._CheckMetaSize(meta.dev_path):
2243
      raise errors.BlockDeviceError("Invalid meta device size")
2244
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2245
    if not cls._IsValidMeta(meta.dev_path):
2246
      raise errors.BlockDeviceError("Cannot initalize meta device")
2247
    return cls(unique_id, children)
2248

    
2249

    
2250
DEV_MAP = {
2251
  constants.LD_LV: LogicalVolume,
2252
  constants.LD_MD_R1: MDRaid1,
2253
  constants.LD_DRBD7: DRBDev,
2254
  constants.LD_DRBD8: DRBD8,
2255
  }
2256

    
2257

    
2258
def FindDevice(dev_type, unique_id, children):
2259
  """Search for an existing, assembled device.
2260

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

2264
  """
2265
  if dev_type not in DEV_MAP:
2266
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2267
  device = DEV_MAP[dev_type](unique_id, children)
2268
  if not device.Attach():
2269
    return None
2270
  return  device
2271

    
2272

    
2273
def AttachOrAssemble(dev_type, unique_id, children):
2274
  """Try to attach or assemble an existing device.
2275

2276
  This will attach to an existing assembled device or will assemble
2277
  the device, as needed, to bring it fully up.
2278

2279
  """
2280
  if dev_type not in DEV_MAP:
2281
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2282
  device = DEV_MAP[dev_type](unique_id, children)
2283
  if not device.Attach():
2284
    device.Assemble()
2285
  if not device.Attach():
2286
    raise errors.BlockDeviceError("Can't find a valid block device for"
2287
                                  " %s/%s/%s" %
2288
                                  (dev_type, unique_id, children))
2289
  return device
2290

    
2291

    
2292
def Create(dev_type, unique_id, children, size):
2293
  """Create a device.
2294

2295
  """
2296
  if dev_type not in DEV_MAP:
2297
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2298
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2299
  return device