Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ a70f34d1

History | View | Annotate | Download (72.1 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
    if result.failed:
1345
      logger.Error("Can't shutdown network: %s" % result.output)
1346
    return not result.failed
1347

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1553
    return result
1554

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

1559
    This writes until we get ENOSPC.
1560

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

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

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

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

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

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

    
1598

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1735
    cls._PARSE_SHOW = bnf
1736

    
1737
    return bnf
1738

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1859
    """
1860
    lhost, lport, rhost, rport = net_info
1861
    if None in net_info:
1862
      # we don't want network connection and actually want to make
1863
      # sure its shutdown
1864
      return cls._ShutdownNet(minor)
1865

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

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

    
1899
  def AddChildren(self, devices):
1900
    """Add a disk to the DRBD device.
1901

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

    
1921
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1922
      raise errors.BlockDeviceError("Can't attach to local storage")
1923
    self._children = devices
1924

    
1925
  def RemoveChildren(self, devices):
1926
    """Detach the drbd device from local storage.
1927

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

    
1950
    if not self._ShutdownLocal(self.minor):
1951
      raise errors.BlockDeviceError("Can't detach from local storage")
1952
    self._children = []
1953

    
1954
  def SetSyncSpeed(self, kbytes):
1955
    """Set the speed of the DRBD syncer.
1956

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

    
1968
  def GetSyncStatus(self):
1969
    """Returns the sync status of the device.
1970

1971
    Returns:
1972
     (sync_percent, estimated_time, is_degraded)
1973

1974
    If sync_percent is None, it means all is ok
1975
    If estimated_time is None, it means we can't esimate
1976
    the time needed, otherwise it's the time left in seconds.
1977

1978

1979
    We set the is_degraded parameter to True on two conditions:
1980
    network not connected or local disk missing.
1981

1982
    We compute the ldisk parameter based on wheter we have a local
1983
    disk or not.
1984

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

    
2014
  def GetStatus(self):
2015
    """Compute the status of the DRBD device
2016

2017
    Note that DRBD devices don't have the STATUS_EXISTING state.
2018

2019
    """
2020
    if self.minor is None and not self.Attach():
2021
      return self.STATUS_UNKNOWN
2022

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

    
2034
    state = mresult.group(2)
2035
    if state == "Primary":
2036
      result = self.STATUS_ONLINE
2037
    else:
2038
      result = self.STATUS_STANDBY
2039

    
2040
    return result
2041

    
2042
  def Open(self, force=False):
2043
    """Make the local state primary.
2044

2045
    If the 'force' parameter is given, the '--do-what-I-say' parameter
2046
    is given. Since this is a pottentialy dangerous operation, the
2047
    force flag should be only given after creation, when it actually
2048
    has to be given.
2049

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

    
2063
  def Close(self):
2064
    """Make the local state secondary.
2065

2066
    This will, of course, fail if the device is in use.
2067

2068
    """
2069
    if self.minor is None and not self.Attach():
2070
      logger.Info("Instance not attached to a device")
2071
      raise errors.BlockDeviceError("Can't find device")
2072
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2073
    if result.failed:
2074
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
2075
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
2076

    
2077
  def Attach(self):
2078
    """Find a DRBD device which matches our config and attach to it.
2079

2080
    In case of partially attached (local device matches but no network
2081
    setup), we perform the network attach. If successful, we re-test
2082
    the attach if can return success.
2083

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

    
2120
    else:
2121
      minor = None
2122

    
2123
    self._SetFromMinor(minor)
2124
    return minor is not None
2125

    
2126
  def Assemble(self):
2127
    """Assemble the drbd.
2128

2129
    Method:
2130
      - if we have a local backing device, we bind to it by:
2131
        - checking the list of used drbd devices
2132
        - check if the local minor use of any of them is our own device
2133
        - if yes, abort?
2134
        - if not, bind
2135
      - if we have a local/remote net info:
2136
        - redo the local backing device step for the remote device
2137
        - check if any drbd device is using the local port,
2138
          if yes abort
2139
        - check if any remote drbd device is using the remote
2140
          port, if yes abort (for now)
2141
        - bind our net port
2142
        - bind the remote net port
2143

2144
    """
2145
    self.Attach()
2146
    if self.minor is not None:
2147
      logger.Info("Already assembled")
2148
      return True
2149

    
2150
    result = super(DRBD8, self).Assemble()
2151
    if not result:
2152
      return result
2153

    
2154
    minor = self._FindUnusedMinor()
2155
    need_localdev_teardown = False
2156
    if self._children and self._children[0] and self._children[1]:
2157
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2158
                                   self._children[1].dev_path)
2159
      if not result:
2160
        return False
2161
      need_localdev_teardown = True
2162
    if self._lhost and self._lport and self._rhost and self._rport:
2163
      result = self._AssembleNet(minor,
2164
                                 (self._lhost, self._lport,
2165
                                  self._rhost, self._rport),
2166
                                 "C")
2167
      if not result:
2168
        if need_localdev_teardown:
2169
          # we will ignore failures from this
2170
          logger.Error("net setup failed, tearing down local device")
2171
          self._ShutdownAll(minor)
2172
        return False
2173
    self._SetFromMinor(minor)
2174
    return True
2175

    
2176
  @classmethod
2177
  def _ShutdownLocal(cls, minor):
2178
    """Detach from the local device.
2179

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

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

    
2189
  @classmethod
2190
  def _ShutdownNet(cls, minor):
2191
    """Disconnect from the remote peer.
2192

2193
    This fails if we don't have a local device.
2194

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

    
2201
  @classmethod
2202
  def _ShutdownAll(cls, minor):
2203
    """Deactivate the device.
2204

2205
    This will, of course, fail if the device is in use.
2206

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

    
2213
  def Shutdown(self):
2214
    """Shutdown the DRBD device.
2215

2216
    """
2217
    if self.minor is None and not self.Attach():
2218
      logger.Info("DRBD device not attached to a device during Shutdown")
2219
      return True
2220
    if not self._ShutdownAll(self.minor):
2221
      return False
2222
    self.minor = None
2223
    self.dev_path = None
2224
    return True
2225

    
2226
  def Remove(self):
2227
    """Stub remove for DRBD devices.
2228

2229
    """
2230
    return self.Shutdown()
2231

    
2232
  @classmethod
2233
  def Create(cls, unique_id, children, size):
2234
    """Create a new DRBD8 device.
2235

2236
    Since DRBD devices are not created per se, just assembled, this
2237
    function only initializes the metadata.
2238

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

    
2253

    
2254
DEV_MAP = {
2255
  constants.LD_LV: LogicalVolume,
2256
  constants.LD_MD_R1: MDRaid1,
2257
  constants.LD_DRBD7: DRBDev,
2258
  constants.LD_DRBD8: DRBD8,
2259
  }
2260

    
2261

    
2262
def FindDevice(dev_type, unique_id, children):
2263
  """Search for an existing, assembled device.
2264

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

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

    
2276

    
2277
def AttachOrAssemble(dev_type, unique_id, children):
2278
  """Try to attach or assemble an existing device.
2279

2280
  This will attach to an existing assembled device or will assemble
2281
  the device, as needed, to bring it fully up.
2282

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

    
2295

    
2296
def Create(dev_type, unique_id, children, size):
2297
  """Create a device.
2298

2299
  """
2300
  if dev_type not in DEV_MAP:
2301
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2302
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2303
  return device