Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ fdbd668d

History | View | Annotate | Download (72.8 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

    
126
      try:
127
        child.Open()
128
      except errors.BlockDeviceError:
129
        for child in self._children:
130
          child.Shutdown()
131
        raise
132

    
133
    if not status:
134
      for child in self._children:
135
        child.Shutdown()
136
    return status
137

    
138
  def Attach(self):
139
    """Find a device which matches our config and attach to it.
140

141
    """
142
    raise NotImplementedError
143

    
144
  def Close(self):
145
    """Notifies that the device will no longer be used for I/O.
146

147
    """
148
    raise NotImplementedError
149

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

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

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

161
    """
162
    raise NotImplementedError
163

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

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

171
    """
172
    raise NotImplementedError
173

    
174
  def Rename(self, new_id):
175
    """Rename this device.
176

177
    This may or may not make sense for a given device type.
178

179
    """
180
    raise NotImplementedError
181

    
182
  def GetStatus(self):
183
    """Return the status of the device.
184

185
    """
186
    raise NotImplementedError
187

    
188
  def Open(self, force=False):
189
    """Make the device ready for use.
190

191
    This makes the device ready for I/O. For now, just the DRBD
192
    devices need this.
193

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

197
    """
198
    raise NotImplementedError
199

    
200
  def Shutdown(self):
201
    """Shut down the device, freeing its children.
202

203
    This undoes the `Assemble()` work, except for the child
204
    assembling; as such, the children on the device are still
205
    assembled after this call.
206

207
    """
208
    raise NotImplementedError
209

    
210
  def SetSyncSpeed(self, speed):
211
    """Adjust the sync speed of the mirror.
212

213
    In case this is not a mirroring device, this is no-op.
214

215
    """
216
    result = True
217
    if self._children:
218
      for child in self._children:
219
        result = result and child.SetSyncSpeed(speed)
220
    return result
221

    
222
  def GetSyncStatus(self):
223
    """Returns the sync status of the device.
224

225
    If this device is a mirroring device, this function returns the
226
    status of the mirror.
227

228
    Returns:
229
     (sync_percent, estimated_time, is_degraded, ldisk)
230

231
    If sync_percent is None, it means the device is not syncing.
232

233
    If estimated_time is None, it means we can't estimate
234
    the time needed, otherwise it's the time left in seconds.
235

236
    If is_degraded is True, it means the device is missing
237
    redundancy. This is usually a sign that something went wrong in
238
    the device setup, if sync_percent is None.
239

240
    The ldisk parameter represents the degradation of the local
241
    data. This is only valid for some devices, the rest will always
242
    return False (not degraded).
243

244
    """
245
    return None, None, False, False
246

    
247

    
248
  def CombinedSyncStatus(self):
249
    """Calculate the mirror status recursively for our children.
250

251
    The return value is the same as for `GetSyncStatus()` except the
252
    minimum percent and maximum time are calculated across our
253
    children.
254

255
    """
256
    min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
257
    if self._children:
258
      for child in self._children:
259
        c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
260
        if min_percent is None:
261
          min_percent = c_percent
262
        elif c_percent is not None:
263
          min_percent = min(min_percent, c_percent)
264
        if max_time is None:
265
          max_time = c_time
266
        elif c_time is not None:
267
          max_time = max(max_time, c_time)
268
        is_degraded = is_degraded or c_degraded
269
        ldisk = ldisk or c_ldisk
270
    return min_percent, max_time, is_degraded, ldisk
271

    
272

    
273
  def SetInfo(self, text):
274
    """Update metadata with info text.
275

276
    Only supported for some device types.
277

278
    """
279
    for child in self._children:
280
      child.SetInfo(text)
281

    
282

    
283
  def __repr__(self):
284
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
285
            (self.__class__, self.unique_id, self._children,
286
             self.major, self.minor, self.dev_path))
287

    
288

    
289
class LogicalVolume(BlockDev):
290
  """Logical Volume block device.
291

292
  """
293
  def __init__(self, unique_id, children):
294
    """Attaches to a LV device.
295

296
    The unique_id is a tuple (vg_name, lv_name)
297

298
    """
299
    super(LogicalVolume, self).__init__(unique_id, children)
300
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
301
      raise ValueError("Invalid configuration data %s" % str(unique_id))
302
    self._vg_name, self._lv_name = unique_id
303
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
304
    self.Attach()
305

    
306
  @classmethod
307
  def Create(cls, unique_id, children, size):
308
    """Create a new logical volume.
309

310
    """
311
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
312
      raise ValueError("Invalid configuration data %s" % str(unique_id))
313
    vg_name, lv_name = unique_id
314
    pvs_info = cls.GetPVInfo(vg_name)
315
    if not pvs_info:
316
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
317
                                    vg_name)
318
    pvs_info.sort()
319
    pvs_info.reverse()
320

    
321
    pvlist = [ pv[1] for pv in pvs_info ]
322
    free_size = sum([ pv[0] for pv in pvs_info ])
323

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

    
335
  @staticmethod
336
  def GetPVInfo(vg_name):
337
    """Get the free space info for PVs in a volume group.
338

339
    Args:
340
      vg_name: the volume group name
341

342
    Returns:
343
      list of (free_space, name) with free_space in mebibytes
344

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

    
364
    return data
365

    
366
  def Remove(self):
367
    """Remove this logical volume.
368

369
    """
370
    if not self.minor and not self.Attach():
371
      # the LV does not exist
372
      return True
373
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
374
                           (self._vg_name, self._lv_name)])
375
    if result.failed:
376
      logger.Error("Can't lvremove: %s" % result.fail_reason)
377

    
378
    return not result.failed
379

    
380
  def Rename(self, new_id):
381
    """Rename this logical volume.
382

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

    
398

    
399
  def Attach(self):
400
    """Attach to an existing LV.
401

402
    This method will try to see if an existing and active LV exists
403
    which matches the our name. If so, its major/minor will be
404
    recorded.
405

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

    
421
  def Assemble(self):
422
    """Assemble the device.
423

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

428
    """
429
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
430
    if result.failed:
431
      logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
432
    return not result.failed
433

    
434
  def Shutdown(self):
435
    """Shutdown the device.
436

437
    This is a no-op for the LV device type, as we don't deactivate the
438
    volumes on shutdown.
439

440
    """
441
    return True
442

    
443
  def GetStatus(self):
444
    """Return the status of the device.
445

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

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

    
469
    return retval
470

    
471
  def GetSyncStatus(self):
472
    """Returns the sync status of the device.
473

474
    If this device is a mirroring device, this function returns the
475
    status of the mirror.
476

477
    Returns:
478
     (sync_percent, estimated_time, is_degraded, ldisk)
479

480
    For logical volumes, sync_percent and estimated_time are always
481
    None (no recovery in progress, as we don't handle the mirrored LV
482
    case). The is_degraded parameter is the inverse of the ldisk
483
    parameter.
484

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

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

    
505
  def Open(self, force=False):
506
    """Make the device ready for I/O.
507

508
    This is a no-op for the LV device type.
509

510
    """
511
    pass
512

    
513
  def Close(self):
514
    """Notifies that the device will no longer be used for I/O.
515

516
    This is a no-op for the LV device type.
517

518
    """
519
    pass
520

    
521
  def Snapshot(self, size):
522
    """Create a snapshot copy of an lvm block device.
523

524
    """
525
    snap_name = self._lv_name + ".snap"
526

    
527
    # remove existing snapshot if found
528
    snap = LogicalVolume((self._vg_name, snap_name), None)
529
    snap.Remove()
530

    
531
    pvs_info = self.GetPVInfo(self._vg_name)
532
    if not pvs_info:
533
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
534
                                    self._vg_name)
535
    pvs_info.sort()
536
    pvs_info.reverse()
537
    free_size, pv_name = pvs_info[0]
538
    if free_size < size:
539
      raise errors.BlockDeviceError("Not enough free space: required %s,"
540
                                    " available %s" % (size, free_size))
541

    
542
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
543
                           "-n%s" % snap_name, self.dev_path])
544
    if result.failed:
545
      raise errors.BlockDeviceError("command: %s error: %s" %
546
                                    (result.cmd, result.fail_reason))
547

    
548
    return snap_name
549

    
550
  def SetInfo(self, text):
551
    """Update metadata with info text.
552

553
    """
554
    BlockDev.SetInfo(self, text)
555

    
556
    # Replace invalid characters
557
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
558
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
559

    
560
    # Only up to 128 characters are allowed
561
    text = text[:128]
562

    
563
    result = utils.RunCmd(["lvchange", "--addtag", text,
564
                           self.dev_path])
565
    if result.failed:
566
      raise errors.BlockDeviceError("Command: %s error: %s" %
567
                                    (result.cmd, result.fail_reason))
568

    
569

    
570
class MDRaid1(BlockDev):
571
  """raid1 device implemented via md.
572

573
  """
574
  def __init__(self, unique_id, children):
575
    super(MDRaid1, self).__init__(unique_id, children)
576
    self.major = 9
577
    self.Attach()
578

    
579
  def Attach(self):
580
    """Find an array which matches our config and attach to it.
581

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

584
    """
585
    minor = self._FindMDByUUID(self.unique_id)
586
    if minor is not None:
587
      self._SetFromMinor(minor)
588
    else:
589
      self.minor = None
590
      self.dev_path = None
591

    
592
    return (minor is not None)
593

    
594
  @staticmethod
595
  def _GetUsedDevs():
596
    """Compute the list of in-use MD devices.
597

598
    It doesn't matter if the used device have other raid level, just
599
    that they are in use.
600

601
    """
602
    mdstat = open("/proc/mdstat", "r")
603
    data = mdstat.readlines()
604
    mdstat.close()
605

    
606
    used_md = {}
607
    valid_line = re.compile("^md([0-9]+) : .*$")
608
    for line in data:
609
      match = valid_line.match(line)
610
      if match:
611
        md_no = int(match.group(1))
612
        used_md[md_no] = line
613

    
614
    return used_md
615

    
616
  @staticmethod
617
  def _GetDevInfo(minor):
618
    """Get info about a MD device.
619

620
    Currently only uuid is returned.
621

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

    
638
  @staticmethod
639
  def _FindUnusedMinor():
640
    """Compute an unused MD minor.
641

642
    This code assumes that there are 256 minors only.
643

644
    """
645
    used_md = MDRaid1._GetUsedDevs()
646
    i = 0
647
    while i < 256:
648
      if i not in used_md:
649
        break
650
      i += 1
651
    if i == 256:
652
      logger.Error("Critical: Out of md minor numbers.")
653
      raise errors.BlockDeviceError("Can't find a free MD minor")
654
    return i
655

    
656
  @classmethod
657
  def _FindMDByUUID(cls, uuid):
658
    """Find the minor of an MD array with a given UUID.
659

660
    """
661
    md_list = cls._GetUsedDevs()
662
    for minor in md_list:
663
      info = cls._GetDevInfo(minor)
664
      if info and info["uuid"] == uuid:
665
        return minor
666
    return None
667

    
668
  @staticmethod
669
  def _ZeroSuperblock(dev_path):
670
    """Zero the possible locations for an MD superblock.
671

672
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
673
    fails in versions 2.x with the same error code as non-writable
674
    device.
675

676
    The superblocks are located at (negative values are relative to
677
    the end of the block device):
678
      - -128k to end for version 0.90 superblock
679
      - -8k to -12k for version 1.0 superblock (included in the above)
680
      - 0k to 4k for version 1.1 superblock
681
      - 4k to 8k for version 1.2 superblock
682

683
    To cover all situations, the zero-ing will be:
684
      - 0k to 128k
685
      - -128k to end
686

687
    As such, the minimum device size must be 128k, otherwise we'll get
688
    I/O errors.
689

690
    Note that this function depends on the fact that one can open,
691
    read and write block devices normally.
692

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

    
711
  @classmethod
712
  def Create(cls, unique_id, children, size):
713
    """Create a new MD raid1 array.
714

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

    
735
    if result.failed:
736
      logger.Error("Can't create md: %s: %s" % (result.fail_reason,
737
                                                result.output))
738
      return None
739
    info = cls._GetDevInfo(minor)
740
    if not info or not "uuid" in info:
741
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
742
      return None
743
    return MDRaid1(info["uuid"], children)
744

    
745
  def Remove(self):
746
    """Stub remove function for MD RAID 1 arrays.
747

748
    We don't remove the superblock right now. Mark a to do.
749

750
    """
751
    #TODO: maybe zero superblock on child devices?
752
    return self.Shutdown()
753

    
754
  def Rename(self, new_id):
755
    """Rename a device.
756

757
    This is not supported for md raid1 devices.
758

759
    """
760
    raise errors.ProgrammerError("Can't rename a md raid1 device")
761

    
762
  def AddChildren(self, devices):
763
    """Add new member(s) to the md raid1.
764

765
    """
766
    if self.minor is None and not self.Attach():
767
      raise errors.BlockDeviceError("Can't attach to device")
768

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

    
786
  def RemoveChildren(self, devices):
787
    """Remove member(s) from the md raid1.
788

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

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

    
827
  def GetStatus(self):
828
    """Return the status of the device.
829

830
    """
831
    self.Attach()
832
    if self.minor is None:
833
      retval = self.STATUS_UNKNOWN
834
    else:
835
      retval = self.STATUS_ONLINE
836
    return retval
837

    
838
  def _SetFromMinor(self, minor):
839
    """Set our parameters based on the given minor.
840

841
    This sets our minor variable and our dev_path.
842

843
    """
844
    self.minor = minor
845
    self.dev_path = "/dev/md%d" % minor
846

    
847
  def Assemble(self):
848
    """Assemble the MD device.
849

850
    At this point we should have:
851
      - list of children devices
852
      - uuid
853

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

    
877
  def Shutdown(self):
878
    """Tear down the MD array.
879

880
    This does a 'mdadm --stop' so after this command, the array is no
881
    longer available.
882

883
    """
884
    if self.minor is None and not self.Attach():
885
      logger.Info("MD object not attached to a device")
886
      return True
887

    
888
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
889
    if result.failed:
890
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
891
      return False
892
    self.minor = None
893
    self.dev_path = None
894
    return True
895

    
896
  def SetSyncSpeed(self, kbytes):
897
    """Set the maximum sync speed for the MD array.
898

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

    
916
  def GetSyncStatus(self):
917
    """Returns the sync status of the device.
918

919
    Returns:
920
     (sync_percent, estimated_time, is_degraded, ldisk)
921

922
    If sync_percent is None, it means all is ok
923
    If estimated_time is None, it means we can't esimate
924
    the time needed, otherwise it's the time left in seconds.
925

926
    The ldisk parameter is always true for MD devices.
927

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

    
956
  def Open(self, force=False):
957
    """Make the device ready for I/O.
958

959
    This is a no-op for the MDRaid1 device type, although we could use
960
    the 2.6.18's new array_state thing.
961

962
    """
963
    pass
964

    
965
  def Close(self):
966
    """Notifies that the device will no longer be used for I/O.
967

968
    This is a no-op for the MDRaid1 device type, but see comment for
969
    `Open()`.
970

971
    """
972
    pass
973

    
974

    
975
class BaseDRBD(BlockDev):
976
  """Base DRBD class.
977

978
  This class contains a few bits of common functionality between the
979
  0.7 and 8.x versions of DRBD.
980

981
  """
982
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
983
                           r" \(api:(\d+)/proto:(\d+)\)")
984
  _DRBD_MAJOR = 147
985
  _ST_UNCONFIGURED = "Unconfigured"
986
  _ST_WFCONNECTION = "WFConnection"
987
  _ST_CONNECTED = "Connected"
988

    
989
  @staticmethod
990
  def _GetProcData():
991
    """Return data from /proc/drbd.
992

993
    """
994
    stat = open("/proc/drbd", "r")
995
    try:
996
      data = stat.read().splitlines()
997
    finally:
998
      stat.close()
999
    if not data:
1000
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
1001
    return data
1002

    
1003
  @staticmethod
1004
  def _MassageProcData(data):
1005
    """Transform the output of _GetProdData into a nicer form.
1006

1007
    Returns:
1008
      a dictionary of minor: joined lines from /proc/drbd for that minor
1009

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

    
1029
  @classmethod
1030
  def _GetVersion(cls):
1031
    """Return the DRBD version.
1032

1033
    This will return a list [k_major, k_minor, k_point, api, proto].
1034

1035
    """
1036
    proc_data = cls._GetProcData()
1037
    first_line = proc_data[0].strip()
1038
    version = cls._VERSION_RE.match(first_line)
1039
    if not version:
1040
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1041
                                    first_line)
1042
    return [int(val) for val in version.groups()]
1043

    
1044
  @staticmethod
1045
  def _DevPath(minor):
1046
    """Return the path to a drbd device for a given minor.
1047

1048
    """
1049
    return "/dev/drbd%d" % minor
1050

    
1051
  @classmethod
1052
  def _GetUsedDevs(cls):
1053
    """Compute the list of used DRBD devices.
1054

1055
    """
1056
    data = cls._GetProcData()
1057

    
1058
    used_devs = {}
1059
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1060
    for line in data:
1061
      match = valid_line.match(line)
1062
      if not match:
1063
        continue
1064
      minor = int(match.group(1))
1065
      state = match.group(2)
1066
      if state == cls._ST_UNCONFIGURED:
1067
        continue
1068
      used_devs[minor] = state, line
1069

    
1070
    return used_devs
1071

    
1072
  def _SetFromMinor(self, minor):
1073
    """Set our parameters based on the given minor.
1074

1075
    This sets our minor variable and our dev_path.
1076

1077
    """
1078
    if minor is None:
1079
      self.minor = self.dev_path = None
1080
    else:
1081
      self.minor = minor
1082
      self.dev_path = self._DevPath(minor)
1083

    
1084
  @staticmethod
1085
  def _CheckMetaSize(meta_device):
1086
    """Check if the given meta device looks like a valid one.
1087

1088
    This currently only check the size, which must be around
1089
    128MiB.
1090

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

    
1110
  def Rename(self, new_id):
1111
    """Rename a device.
1112

1113
    This is not supported for drbd devices.
1114

1115
    """
1116
    raise errors.ProgrammerError("Can't rename a drbd device")
1117

    
1118

    
1119
class DRBDev(BaseDRBD):
1120
  """DRBD block device.
1121

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

1126
  The unique_id for the drbd device is the (local_ip, local_port,
1127
  remote_ip, remote_port) tuple, and it must have two children: the
1128
  data device and the meta_device. The meta device is checked for
1129
  valid size and is zeroed on create.
1130

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

    
1141
    if len(children) != 2:
1142
      raise ValueError("Invalid configuration data %s" % str(children))
1143
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1144
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1145
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1146
    self.Attach()
1147

    
1148
  @classmethod
1149
  def _FindUnusedMinor(cls):
1150
    """Find an unused DRBD device.
1151

1152
    """
1153
    data = cls._GetProcData()
1154

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

    
1163
  @classmethod
1164
  def _GetDevInfo(cls, minor):
1165
    """Get details about a given DRBD minor.
1166

1167
    This return, if available, the local backing device in (major,
1168
    minor) formant and the local and remote (ip, port) information.
1169

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

    
1214
  def _MatchesLocal(self, info):
1215
    """Test if our local config matches with an existing device.
1216

1217
    The parameter should be as returned from `_GetDevInfo()`. This
1218
    method tests if our local backing device is the same as the one in
1219
    the info parameter, in effect testing if we look like the given
1220
    device.
1221

1222
    """
1223
    if not ("local_dev" in info and "meta_dev" in info and
1224
            "meta_index" in info):
1225
      return False
1226

    
1227
    backend = self._children[0]
1228
    if backend is not None:
1229
      retval = (info["local_dev"] == (backend.major, backend.minor))
1230
    else:
1231
      retval = (info["local_dev"] == (0, 0))
1232
    meta = self._children[1]
1233
    if meta is not None:
1234
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1235
      retval = retval and (info["meta_index"] == 0)
1236
    else:
1237
      retval = retval and (info["meta_dev"] == "internal" and
1238
                           info["meta_index"] == -1)
1239
    return retval
1240

    
1241
  def _MatchesNet(self, info):
1242
    """Test if our network config matches with an existing device.
1243

1244
    The parameter should be as returned from `_GetDevInfo()`. This
1245
    method tests if our network configuration is the same as the one
1246
    in the info parameter, in effect testing if we look like the given
1247
    device.
1248

1249
    """
1250
    if (((self._lhost is None and not ("local_addr" in info)) and
1251
         (self._rhost is None and not ("remote_addr" in info)))):
1252
      return True
1253

    
1254
    if self._lhost is None:
1255
      return False
1256

    
1257
    if not ("local_addr" in info and
1258
            "remote_addr" in info):
1259
      return False
1260

    
1261
    retval = (info["local_addr"] == (self._lhost, self._lport))
1262
    retval = (retval and
1263
              info["remote_addr"] == (self._rhost, self._rport))
1264
    return retval
1265

    
1266
  @classmethod
1267
  def _AssembleLocal(cls, minor, backend, meta):
1268
    """Configure the local part of a DRBD device.
1269

1270
    This is the first thing that must be done on an unconfigured DRBD
1271
    device. And it must be done only once.
1272

1273
    """
1274
    if not cls._CheckMetaSize(meta):
1275
      return False
1276
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1277
                           backend, meta, "0", "-e", "detach"])
1278
    if result.failed:
1279
      logger.Error("Can't attach local disk: %s" % result.output)
1280
    return not result.failed
1281

    
1282
  @classmethod
1283
  def _ShutdownLocal(cls, minor):
1284
    """Detach from the local device.
1285

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

1289
    """
1290
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1291
    if result.failed:
1292
      logger.Error("Can't detach local device: %s" % result.output)
1293
    return not result.failed
1294

    
1295
  @staticmethod
1296
  def _ShutdownAll(minor):
1297
    """Deactivate the device.
1298

1299
    This will, of course, fail if the device is in use.
1300

1301
    """
1302
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1303
    if result.failed:
1304
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1305
    return not result.failed
1306

    
1307
  @classmethod
1308
  def _AssembleNet(cls, minor, net_info, protocol):
1309
    """Configure the network part of the device.
1310

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

1318
    """
1319
    lhost, lport, rhost, rport = net_info
1320
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1321
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1322
                           protocol])
1323
    if result.failed:
1324
      logger.Error("Can't setup network for dbrd device: %s" %
1325
                   result.fail_reason)
1326
      return False
1327

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

    
1346
  @classmethod
1347
  def _ShutdownNet(cls, minor):
1348
    """Disconnect from the remote peer.
1349

1350
    This fails if we don't have a local device.
1351

1352
    """
1353
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1354
    if result.failed:
1355
      logger.Error("Can't shutdown network: %s" % result.output)
1356
    return not result.failed
1357

    
1358
  def Assemble(self):
1359
    """Assemble the drbd.
1360

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

1376
    """
1377
    self.Attach()
1378
    if self.minor is not None:
1379
      logger.Info("Already assembled")
1380
      return True
1381

    
1382
    result = super(DRBDev, self).Assemble()
1383
    if not result:
1384
      return result
1385

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

    
1408
  def Shutdown(self):
1409
    """Shutdown the DRBD device.
1410

1411
    """
1412
    if self.minor is None and not self.Attach():
1413
      logger.Info("DRBD device not attached to a device during Shutdown")
1414
      return True
1415
    if not self._ShutdownAll(self.minor):
1416
      return False
1417
    self.minor = None
1418
    self.dev_path = None
1419
    return True
1420

    
1421
  def Attach(self):
1422
    """Find a DRBD device which matches our config and attach to it.
1423

1424
    In case of partially attached (local device matches but no network
1425
    setup), we perform the network attach. If successful, we re-test
1426
    the attach if can return success.
1427

1428
    """
1429
    for minor in self._GetUsedDevs():
1430
      info = self._GetDevInfo(minor)
1431
      match_l = self._MatchesLocal(info)
1432
      match_r = self._MatchesNet(info)
1433
      if match_l and match_r:
1434
        break
1435
      if match_l and not match_r and "local_addr" not in info:
1436
        res_r = self._AssembleNet(minor,
1437
                                  (self._lhost, self._lport,
1438
                                   self._rhost, self._rport),
1439
                                  "C")
1440
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1441
          break
1442
    else:
1443
      minor = None
1444

    
1445
    self._SetFromMinor(minor)
1446
    return minor is not None
1447

    
1448
  def Open(self, force=False):
1449
    """Make the local state primary.
1450

1451
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1452
    is given. Since this is a pottentialy dangerous operation, the
1453
    force flag should be only given after creation, when it actually
1454
    has to be given.
1455

1456
    """
1457
    if self.minor is None and not self.Attach():
1458
      logger.Error("DRBD cannot attach to a device during open")
1459
      return False
1460
    cmd = ["drbdsetup", self.dev_path, "primary"]
1461
    if force:
1462
      cmd.append("--do-what-I-say")
1463
    result = utils.RunCmd(cmd)
1464
    if result.failed:
1465
      msg = ("Can't make drbd device primary: %s" % result.output)
1466
      logger.Error(msg)
1467
      raise errors.BlockDeviceError(msg)
1468

    
1469
  def Close(self):
1470
    """Make the local state secondary.
1471

1472
    This will, of course, fail if the device is in use.
1473

1474
    """
1475
    if self.minor is None and not self.Attach():
1476
      logger.Info("Instance not attached to a device")
1477
      raise errors.BlockDeviceError("Can't find device")
1478
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1479
    if result.failed:
1480
      msg = ("Can't switch drbd device to"
1481
             " secondary: %s" % result.output)
1482
      logger.Error(msg)
1483
      raise errors.BlockDeviceError(msg)
1484

    
1485
  def SetSyncSpeed(self, kbytes):
1486
    """Set the speed of the DRBD syncer.
1487

1488
    """
1489
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1490
    if self.minor is None:
1491
      logger.Info("Instance not attached to a device")
1492
      return False
1493
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1494
                           kbytes])
1495
    if result.failed:
1496
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1497
    return not result.failed and children_result
1498

    
1499
  def GetSyncStatus(self):
1500
    """Returns the sync status of the device.
1501

1502
    Returns:
1503
     (sync_percent, estimated_time, is_degraded, ldisk)
1504

1505
    If sync_percent is None, it means all is ok
1506
    If estimated_time is None, it means we can't esimate
1507
    the time needed, otherwise it's the time left in seconds.
1508

1509
    The ldisk parameter will be returned as True, since the DRBD7
1510
    devices have not been converted.
1511

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

    
1539
  def GetStatus(self):
1540
    """Compute the status of the DRBD device
1541

1542
    Note that DRBD devices don't have the STATUS_EXISTING state.
1543

1544
    """
1545
    if self.minor is None and not self.Attach():
1546
      return self.STATUS_UNKNOWN
1547

    
1548
    data = self._GetProcData()
1549
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1550
                       self.minor)
1551
    for line in data:
1552
      mresult = match.match(line)
1553
      if mresult:
1554
        break
1555
    else:
1556
      logger.Error("Can't find myself!")
1557
      return self.STATUS_UNKNOWN
1558

    
1559
    state = mresult.group(2)
1560
    if state == "Primary":
1561
      result = self.STATUS_ONLINE
1562
    else:
1563
      result = self.STATUS_STANDBY
1564

    
1565
    return result
1566

    
1567
  @staticmethod
1568
  def _ZeroDevice(device):
1569
    """Zero a device.
1570

1571
    This writes until we get ENOSPC.
1572

1573
    """
1574
    f = open(device, "w")
1575
    buf = "\0" * 1048576
1576
    try:
1577
      while True:
1578
        f.write(buf)
1579
    except IOError, err:
1580
      if err.errno != errno.ENOSPC:
1581
        raise
1582

    
1583
  @classmethod
1584
  def Create(cls, unique_id, children, size):
1585
    """Create a new DRBD device.
1586

1587
    Since DRBD devices are not created per se, just assembled, this
1588
    function just zeroes the meta device.
1589

1590
    """
1591
    if len(children) != 2:
1592
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1593
    meta = children[1]
1594
    meta.Assemble()
1595
    if not meta.Attach():
1596
      raise errors.BlockDeviceError("Can't attach to meta device")
1597
    if not cls._CheckMetaSize(meta.dev_path):
1598
      raise errors.BlockDeviceError("Invalid meta device")
1599
    logger.Info("Started zeroing device %s" % meta.dev_path)
1600
    cls._ZeroDevice(meta.dev_path)
1601
    logger.Info("Done zeroing device %s" % meta.dev_path)
1602
    return cls(unique_id, children)
1603

    
1604
  def Remove(self):
1605
    """Stub remove for DRBD devices.
1606

1607
    """
1608
    return self.Shutdown()
1609

    
1610

    
1611
class DRBD8(BaseDRBD):
1612
  """DRBD v8.x block device.
1613

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

1618
  The unique_id for the drbd device is the (local_ip, local_port,
1619
  remote_ip, remote_port) tuple, and it must have two children: the
1620
  data device and the meta_device. The meta device is checked for
1621
  valid size and is zeroed on create.
1622

1623
  """
1624
  _MAX_MINORS = 255
1625
  _PARSE_SHOW = None
1626

    
1627
  def __init__(self, unique_id, children):
1628
    if children and children.count(None) > 0:
1629
      children = []
1630
    super(DRBD8, self).__init__(unique_id, children)
1631
    self.major = self._DRBD_MAJOR
1632
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1633
    if kmaj != 8:
1634
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1635
                                    " requested ganeti usage: kernel is"
1636
                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1637

    
1638
    if len(children) not in (0, 2):
1639
      raise ValueError("Invalid configuration data %s" % str(children))
1640
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1641
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1642
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1643
    self.Attach()
1644

    
1645
  @classmethod
1646
  def _InitMeta(cls, minor, dev_path):
1647
    """Initialize a meta device.
1648

1649
    This will not work if the given minor is in use.
1650

1651
    """
1652
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1653
                           "v08", dev_path, "0", "create-md"])
1654
    if result.failed:
1655
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1656
                                    result.output)
1657

    
1658
  @classmethod
1659
  def _FindUnusedMinor(cls):
1660
    """Find an unused DRBD device.
1661

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

1665
    """
1666
    data = cls._GetProcData()
1667

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

    
1686
  @classmethod
1687
  def _IsValidMeta(cls, meta_device):
1688
    """Check if the given meta device looks like a valid one.
1689

1690
    """
1691
    minor = cls._FindUnusedMinor()
1692
    minor_path = cls._DevPath(minor)
1693
    result = utils.RunCmd(["drbdmeta", minor_path,
1694
                           "v08", meta_device, "0",
1695
                           "dstate"])
1696
    if result.failed:
1697
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1698
      return False
1699
    return True
1700

    
1701
  @classmethod
1702
  def _GetShowParser(cls):
1703
    """Return a parser for `drbd show` output.
1704

1705
    This will either create or return an already-create parser for the
1706
    output of the command `drbd show`.
1707

1708
    """
1709
    if cls._PARSE_SHOW is not None:
1710
      return cls._PARSE_SHOW
1711

    
1712
    # pyparsing setup
1713
    lbrace = pyp.Literal("{").suppress()
1714
    rbrace = pyp.Literal("}").suppress()
1715
    semi = pyp.Literal(";").suppress()
1716
    # this also converts the value to an int
1717
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1718

    
1719
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1720
    defa = pyp.Literal("_is_default").suppress()
1721
    dbl_quote = pyp.Literal('"').suppress()
1722

    
1723
    keyword = pyp.Word(pyp.alphanums + '-')
1724

    
1725
    # value types
1726
    value = pyp.Word(pyp.alphanums + '_-/.:')
1727
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1728
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1729
                 number)
1730
    # meta device, extended syntax
1731
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1732
                  number + pyp.Word(']').suppress())
1733

    
1734
    # a statement
1735
    stmt = (~rbrace + keyword + ~lbrace +
1736
            (addr_port ^ value ^ quoted ^ meta_value) +
1737
            pyp.Optional(defa) + semi +
1738
            pyp.Optional(pyp.restOfLine).suppress())
1739

    
1740
    # an entire section
1741
    section_name = pyp.Word(pyp.alphas + '_')
1742
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1743

    
1744
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1745
    bnf.ignore(comment)
1746

    
1747
    cls._PARSE_SHOW = bnf
1748

    
1749
    return bnf
1750

    
1751
  @classmethod
1752
  def _GetShowData(cls, minor):
1753
    """Return the `drbdsetup show` data for a minor.
1754

1755
    """
1756
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1757
    if result.failed:
1758
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1759
      return None
1760
    return result.stdout
1761

    
1762
  @classmethod
1763
  def _GetDevInfo(cls, out):
1764
    """Parse details about a given DRBD minor.
1765

1766
    This return, if available, the local backing device (as a path)
1767
    and the local and remote (ip, port) information from a string
1768
    containing the output of the `drbdsetup show` command as returned
1769
    by _GetShowData.
1770

1771
    """
1772
    data = {}
1773
    if not out:
1774
      return data
1775

    
1776
    bnf = cls._GetShowParser()
1777
    # run pyparse
1778

    
1779
    try:
1780
      results = bnf.parseString(out)
1781
    except pyp.ParseException, err:
1782
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1783
                                    str(err))
1784

    
1785
    # and massage the results into our desired format
1786
    for section in results:
1787
      sname = section[0]
1788
      if sname == "_this_host":
1789
        for lst in section[1:]:
1790
          if lst[0] == "disk":
1791
            data["local_dev"] = lst[1]
1792
          elif lst[0] == "meta-disk":
1793
            data["meta_dev"] = lst[1]
1794
            data["meta_index"] = lst[2]
1795
          elif lst[0] == "address":
1796
            data["local_addr"] = tuple(lst[1:])
1797
      elif sname == "_remote_host":
1798
        for lst in section[1:]:
1799
          if lst[0] == "address":
1800
            data["remote_addr"] = tuple(lst[1:])
1801
    return data
1802

    
1803
  def _MatchesLocal(self, info):
1804
    """Test if our local config matches with an existing device.
1805

1806
    The parameter should be as returned from `_GetDevInfo()`. This
1807
    method tests if our local backing device is the same as the one in
1808
    the info parameter, in effect testing if we look like the given
1809
    device.
1810

1811
    """
1812
    if self._children:
1813
      backend, meta = self._children
1814
    else:
1815
      backend = meta = None
1816

    
1817
    if backend is not None:
1818
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1819
    else:
1820
      retval = ("local_dev" not in info)
1821

    
1822
    if meta is not None:
1823
      retval = retval and ("meta_dev" in info and
1824
                           info["meta_dev"] == meta.dev_path)
1825
      retval = retval and ("meta_index" in info and
1826
                           info["meta_index"] == 0)
1827
    else:
1828
      retval = retval and ("meta_dev" not in info and
1829
                           "meta_index" not in info)
1830
    return retval
1831

    
1832
  def _MatchesNet(self, info):
1833
    """Test if our network config matches with an existing device.
1834

1835
    The parameter should be as returned from `_GetDevInfo()`. This
1836
    method tests if our network configuration is the same as the one
1837
    in the info parameter, in effect testing if we look like the given
1838
    device.
1839

1840
    """
1841
    if (((self._lhost is None and not ("local_addr" in info)) and
1842
         (self._rhost is None and not ("remote_addr" in info)))):
1843
      return True
1844

    
1845
    if self._lhost is None:
1846
      return False
1847

    
1848
    if not ("local_addr" in info and
1849
            "remote_addr" in info):
1850
      return False
1851

    
1852
    retval = (info["local_addr"] == (self._lhost, self._lport))
1853
    retval = (retval and
1854
              info["remote_addr"] == (self._rhost, self._rport))
1855
    return retval
1856

    
1857
  @classmethod
1858
  def _AssembleLocal(cls, minor, backend, meta):
1859
    """Configure the local part of a DRBD device.
1860

1861
    This is the first thing that must be done on an unconfigured DRBD
1862
    device. And it must be done only once.
1863

1864
    """
1865
    if not cls._IsValidMeta(meta):
1866
      return False
1867
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1868
                           backend, meta, "0", "-e", "detach",
1869
                           "--create-device"])
1870
    if result.failed:
1871
      logger.Error("Can't attach local disk: %s" % result.output)
1872
    return not result.failed
1873

    
1874
  @classmethod
1875
  def _AssembleNet(cls, minor, net_info, protocol,
1876
                   dual_pri=False, hmac=None, secret=None):
1877
    """Configure the network part of the device.
1878

1879
    """
1880
    lhost, lport, rhost, rport = net_info
1881
    if None in net_info:
1882
      # we don't want network connection and actually want to make
1883
      # sure its shutdown
1884
      return cls._ShutdownNet(minor)
1885

    
1886
    args = ["drbdsetup", cls._DevPath(minor), "net",
1887
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1888
            "-A", "discard-zero-changes",
1889
            "-B", "consensus",
1890
            ]
1891
    if dual_pri:
1892
      args.append("-m")
1893
    if hmac and secret:
1894
      args.extend(["-a", hmac, "-x", secret])
1895
    result = utils.RunCmd(args)
1896
    if result.failed:
1897
      logger.Error("Can't setup network for dbrd device: %s" %
1898
                   result.fail_reason)
1899
      return False
1900

    
1901
    timeout = time.time() + 10
1902
    ok = False
1903
    while time.time() < timeout:
1904
      info = cls._GetDevInfo(cls._GetShowData(minor))
1905
      if not "local_addr" in info or not "remote_addr" in info:
1906
        time.sleep(1)
1907
        continue
1908
      if (info["local_addr"] != (lhost, lport) or
1909
          info["remote_addr"] != (rhost, rport)):
1910
        time.sleep(1)
1911
        continue
1912
      ok = True
1913
      break
1914
    if not ok:
1915
      logger.Error("Timeout while configuring network")
1916
      return False
1917
    return True
1918

    
1919
  def AddChildren(self, devices):
1920
    """Add a disk to the DRBD device.
1921

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

    
1941
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1942
      raise errors.BlockDeviceError("Can't attach to local storage")
1943
    self._children = devices
1944

    
1945
  def RemoveChildren(self, devices):
1946
    """Detach the drbd device from local storage.
1947

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

    
1970
    if not self._ShutdownLocal(self.minor):
1971
      raise errors.BlockDeviceError("Can't detach from local storage")
1972
    self._children = []
1973

    
1974
  def SetSyncSpeed(self, kbytes):
1975
    """Set the speed of the DRBD syncer.
1976

1977
    """
1978
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1979
    if self.minor is None:
1980
      logger.Info("Instance not attached to a device")
1981
      return False
1982
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1983
                           kbytes])
1984
    if result.failed:
1985
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1986
    return not result.failed and children_result
1987

    
1988
  def GetSyncStatus(self):
1989
    """Returns the sync status of the device.
1990

1991
    Returns:
1992
     (sync_percent, estimated_time, is_degraded)
1993

1994
    If sync_percent is None, it means all is ok
1995
    If estimated_time is None, it means we can't esimate
1996
    the time needed, otherwise it's the time left in seconds.
1997

1998

1999
    We set the is_degraded parameter to True on two conditions:
2000
    network not connected or local disk missing.
2001

2002
    We compute the ldisk parameter based on wheter we have a local
2003
    disk or not.
2004

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

    
2034
  def GetStatus(self):
2035
    """Compute the status of the DRBD device
2036

2037
    Note that DRBD devices don't have the STATUS_EXISTING state.
2038

2039
    """
2040
    if self.minor is None and not self.Attach():
2041
      return self.STATUS_UNKNOWN
2042

    
2043
    data = self._GetProcData()
2044
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
2045
                       self.minor)
2046
    for line in data:
2047
      mresult = match.match(line)
2048
      if mresult:
2049
        break
2050
    else:
2051
      logger.Error("Can't find myself!")
2052
      return self.STATUS_UNKNOWN
2053

    
2054
    state = mresult.group(2)
2055
    if state == "Primary":
2056
      result = self.STATUS_ONLINE
2057
    else:
2058
      result = self.STATUS_STANDBY
2059

    
2060
    return result
2061

    
2062
  def Open(self, force=False):
2063
    """Make the local state primary.
2064

2065
    If the 'force' parameter is given, the '--do-what-I-say' parameter
2066
    is given. Since this is a pottentialy dangerous operation, the
2067
    force flag should be only given after creation, when it actually
2068
    has to be given.
2069

2070
    """
2071
    if self.minor is None and not self.Attach():
2072
      logger.Error("DRBD cannot attach to a device during open")
2073
      return False
2074
    cmd = ["drbdsetup", self.dev_path, "primary"]
2075
    if force:
2076
      cmd.append("-o")
2077
    result = utils.RunCmd(cmd)
2078
    if result.failed:
2079
      msg = ("Can't make drbd device primary: %s" % result.output)
2080
      logger.Error(msg)
2081
      raise errors.BlockDeviceError(msg)
2082

    
2083
  def Close(self):
2084
    """Make the local state secondary.
2085

2086
    This will, of course, fail if the device is in use.
2087

2088
    """
2089
    if self.minor is None and not self.Attach():
2090
      logger.Info("Instance not attached to a device")
2091
      raise errors.BlockDeviceError("Can't find device")
2092
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2093
    if result.failed:
2094
      msg = ("Can't switch drbd device to"
2095
             " secondary: %s" % result.output)
2096
      logger.Error(msg)
2097
      raise errors.BlockDeviceError(msg)
2098

    
2099
  def Attach(self):
2100
    """Find a DRBD device which matches our config and attach to it.
2101

2102
    In case of partially attached (local device matches but no network
2103
    setup), we perform the network attach. If successful, we re-test
2104
    the attach if can return success.
2105

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

    
2143
    else:
2144
      minor = None
2145

    
2146
    self._SetFromMinor(minor)
2147
    return minor is not None
2148

    
2149
  def Assemble(self):
2150
    """Assemble the drbd.
2151

2152
    Method:
2153
      - if we have a local backing device, we bind to it by:
2154
        - checking the list of used drbd devices
2155
        - check if the local minor use of any of them is our own device
2156
        - if yes, abort?
2157
        - if not, bind
2158
      - if we have a local/remote net info:
2159
        - redo the local backing device step for the remote device
2160
        - check if any drbd device is using the local port,
2161
          if yes abort
2162
        - check if any remote drbd device is using the remote
2163
          port, if yes abort (for now)
2164
        - bind our net port
2165
        - bind the remote net port
2166

2167
    """
2168
    self.Attach()
2169
    if self.minor is not None:
2170
      logger.Info("Already assembled")
2171
      return True
2172

    
2173
    result = super(DRBD8, self).Assemble()
2174
    if not result:
2175
      return result
2176

    
2177
    minor = self._FindUnusedMinor()
2178
    need_localdev_teardown = False
2179
    if self._children and self._children[0] and self._children[1]:
2180
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2181
                                   self._children[1].dev_path)
2182
      if not result:
2183
        return False
2184
      need_localdev_teardown = True
2185
    if self._lhost and self._lport and self._rhost and self._rport:
2186
      result = self._AssembleNet(minor,
2187
                                 (self._lhost, self._lport,
2188
                                  self._rhost, self._rport),
2189
                                 "C")
2190
      if not result:
2191
        if need_localdev_teardown:
2192
          # we will ignore failures from this
2193
          logger.Error("net setup failed, tearing down local device")
2194
          self._ShutdownAll(minor)
2195
        return False
2196
    self._SetFromMinor(minor)
2197
    return True
2198

    
2199
  @classmethod
2200
  def _ShutdownLocal(cls, minor):
2201
    """Detach from the local device.
2202

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

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

    
2212
  @classmethod
2213
  def _ShutdownNet(cls, minor):
2214
    """Disconnect from the remote peer.
2215

2216
    This fails if we don't have a local device.
2217

2218
    """
2219
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2220
    if result.failed:
2221
      logger.Error("Can't shutdown network: %s" % result.output)
2222
    return not result.failed
2223

    
2224
  @classmethod
2225
  def _ShutdownAll(cls, minor):
2226
    """Deactivate the device.
2227

2228
    This will, of course, fail if the device is in use.
2229

2230
    """
2231
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2232
    if result.failed:
2233
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2234
    return not result.failed
2235

    
2236
  def Shutdown(self):
2237
    """Shutdown the DRBD device.
2238

2239
    """
2240
    if self.minor is None and not self.Attach():
2241
      logger.Info("DRBD device not attached to a device during Shutdown")
2242
      return True
2243
    if not self._ShutdownAll(self.minor):
2244
      return False
2245
    self.minor = None
2246
    self.dev_path = None
2247
    return True
2248

    
2249
  def Remove(self):
2250
    """Stub remove for DRBD devices.
2251

2252
    """
2253
    return self.Shutdown()
2254

    
2255
  @classmethod
2256
  def Create(cls, unique_id, children, size):
2257
    """Create a new DRBD8 device.
2258

2259
    Since DRBD devices are not created per se, just assembled, this
2260
    function only initializes the metadata.
2261

2262
    """
2263
    if len(children) != 2:
2264
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2265
    meta = children[1]
2266
    meta.Assemble()
2267
    if not meta.Attach():
2268
      raise errors.BlockDeviceError("Can't attach to meta device")
2269
    if not cls._CheckMetaSize(meta.dev_path):
2270
      raise errors.BlockDeviceError("Invalid meta device size")
2271
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2272
    if not cls._IsValidMeta(meta.dev_path):
2273
      raise errors.BlockDeviceError("Cannot initalize meta device")
2274
    return cls(unique_id, children)
2275

    
2276

    
2277
DEV_MAP = {
2278
  constants.LD_LV: LogicalVolume,
2279
  constants.LD_MD_R1: MDRaid1,
2280
  constants.LD_DRBD7: DRBDev,
2281
  constants.LD_DRBD8: DRBD8,
2282
  }
2283

    
2284

    
2285
def FindDevice(dev_type, unique_id, children):
2286
  """Search for an existing, assembled device.
2287

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

2291
  """
2292
  if dev_type not in DEV_MAP:
2293
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2294
  device = DEV_MAP[dev_type](unique_id, children)
2295
  if not device.Attach():
2296
    return None
2297
  return  device
2298

    
2299

    
2300
def AttachOrAssemble(dev_type, unique_id, children):
2301
  """Try to attach or assemble an existing device.
2302

2303
  This will attach to an existing assembled device or will assemble
2304
  the device, as needed, to bring it fully up.
2305

2306
  """
2307
  if dev_type not in DEV_MAP:
2308
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2309
  device = DEV_MAP[dev_type](unique_id, children)
2310
  if not device.Attach():
2311
    device.Assemble()
2312
    if not device.Attach():
2313
      raise errors.BlockDeviceError("Can't find a valid block device for"
2314
                                    " %s/%s/%s" %
2315
                                    (dev_type, unique_id, children))
2316
  return device
2317

    
2318

    
2319
def Create(dev_type, unique_id, children, size):
2320
  """Create a device.
2321

2322
  """
2323
  if dev_type not in DEV_MAP:
2324
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2325
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2326
  return device