Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ ce838ee3

History | View | Annotate | Download (68.9 kB)

1
#
2
#
3

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

    
21

    
22
"""Block device abstraction"""
23

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

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

    
34

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

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

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

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

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

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

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

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

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

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

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

    
97
  def __init__(self, unique_id, children):
98
    self._children = children
99
    self.dev_path = None
100
    self.unique_id = unique_id
101
    self.major = None
102
    self.minor = None
103

    
104
  def Assemble(self):
105
    """Assemble the device from its components.
106

107
    If this is a plain block device (e.g. LVM) than assemble does
108
    nothing, as the LVM has no children and we don't put logical
109
    volumes offline.
110

111
    One guarantee is that after the device has been assembled, it
112
    knows its major/minor numbers. This allows other devices (usually
113
    parents) to probe correctly for their children.
114

115
    """
116
    status = True
117
    for child in self._children:
118
      if not isinstance(child, BlockDev):
119
        raise TypeError("Invalid child passed of type '%s'" % type(child))
120
      if not status:
121
        break
122
      status = status and child.Assemble()
123
      if not status:
124
        break
125
      status = status and child.Open()
126

    
127
    if not status:
128
      for child in self._children:
129
        child.Shutdown()
130
    return status
131

    
132
  def Attach(self):
133
    """Find a device which matches our config and attach to it.
134

135
    """
136
    raise NotImplementedError
137

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

141
    """
142
    raise NotImplementedError
143

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

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

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

155
    """
156
    raise NotImplementedError
157

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

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

165
    """
166
    raise NotImplementedError
167

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

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

173
    """
174
    raise NotImplementedError
175

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

179
    """
180
    raise NotImplementedError
181

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

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

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

191
    """
192
    raise NotImplementedError
193

    
194
  def Shutdown(self):
195
    """Shut down the device, freeing its children.
196

197
    This undoes the `Assemble()` work, except for the child
198
    assembling; as such, the children on the device are still
199
    assembled after this call.
200

201
    """
202
    raise NotImplementedError
203

    
204
  def SetSyncSpeed(self, speed):
205
    """Adjust the sync speed of the mirror.
206

207
    In case this is not a mirroring device, this is no-op.
208

209
    """
210
    result = True
211
    if self._children:
212
      for child in self._children:
213
        result = result and child.SetSyncSpeed(speed)
214
    return result
215

    
216
  def GetSyncStatus(self):
217
    """Returns the sync status of the device.
218

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

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

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

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

    
235

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

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

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

    
259

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

263
    Only supported for some device types.
264

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

    
269

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

    
275

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

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

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

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

    
293

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

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

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

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

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

327
    Args:
328
      vg_name: the volume group name
329

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

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

    
352
    return data
353

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

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

    
366
    return not result.failed
367

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

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

    
384
  def Attach(self):
385
    """Attach to an existing LV.
386

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

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

    
406
  def Assemble(self):
407
    """Assemble the device.
408

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

412
    """
413
    return True
414

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

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

421
    """
422
    return True
423

    
424
  def GetStatus(self):
425
    """Return the status of the device.
426

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

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

    
450
    return retval
451

    
452
  def Open(self, force=False):
453
    """Make the device ready for I/O.
454

455
    This is a no-op for the LV device type.
456

457
    """
458
    return True
459

    
460
  def Close(self):
461
    """Notifies that the device will no longer be used for I/O.
462

463
    This is a no-op for the LV device type.
464

465
    """
466
    return True
467

    
468
  def Snapshot(self, size):
469
    """Create a snapshot copy of an lvm block device.
470

471
    """
472
    snap_name = self._lv_name + ".snap"
473

    
474
    # remove existing snapshot if found
475
    snap = LogicalVolume((self._vg_name, snap_name), None)
476
    snap.Remove()
477

    
478
    pvs_info = self.GetPVInfo(self._vg_name)
479
    if not pvs_info:
480
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
481
                                    self._vg_name)
482
    pvs_info.sort()
483
    pvs_info.reverse()
484
    free_size, pv_name = pvs_info[0]
485
    if free_size < size:
486
      raise errors.BlockDeviceError("Not enough free space: required %s,"
487
                                    " available %s" % (size, free_size))
488

    
489
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
490
                           "-n%s" % snap_name, self.dev_path])
491
    if result.failed:
492
      raise errors.BlockDeviceError("command: %s error: %s" %
493
                                    (result.cmd, result.fail_reason))
494

    
495
    return snap_name
496

    
497
  def SetInfo(self, text):
498
    """Update metadata with info text.
499

500
    """
501
    BlockDev.SetInfo(self, text)
502

    
503
    # Replace invalid characters
504
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
505
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
506

    
507
    # Only up to 128 characters are allowed
508
    text = text[:128]
509

    
510
    result = utils.RunCmd(["lvchange", "--addtag", text,
511
                           self.dev_path])
512
    if result.failed:
513
      raise errors.BlockDeviceError("Command: %s error: %s" %
514
                                    (result.cmd, result.fail_reason))
515

    
516

    
517
class MDRaid1(BlockDev):
518
  """raid1 device implemented via md.
519

520
  """
521
  def __init__(self, unique_id, children):
522
    super(MDRaid1, self).__init__(unique_id, children)
523
    self.major = 9
524
    self.Attach()
525

    
526
  def Attach(self):
527
    """Find an array which matches our config and attach to it.
528

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

531
    """
532
    minor = self._FindMDByUUID(self.unique_id)
533
    if minor is not None:
534
      self._SetFromMinor(minor)
535
    else:
536
      self.minor = None
537
      self.dev_path = None
538

    
539
    return (minor is not None)
540

    
541
  @staticmethod
542
  def _GetUsedDevs():
543
    """Compute the list of in-use MD devices.
544

545
    It doesn't matter if the used device have other raid level, just
546
    that they are in use.
547

548
    """
549
    mdstat = open("/proc/mdstat", "r")
550
    data = mdstat.readlines()
551
    mdstat.close()
552

    
553
    used_md = {}
554
    valid_line = re.compile("^md([0-9]+) : .*$")
555
    for line in data:
556
      match = valid_line.match(line)
557
      if match:
558
        md_no = int(match.group(1))
559
        used_md[md_no] = line
560

    
561
    return used_md
562

    
563
  @staticmethod
564
  def _GetDevInfo(minor):
565
    """Get info about a MD device.
566

567
    Currently only uuid is returned.
568

569
    """
570
    result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
571
    if result.failed:
572
      logger.Error("Can't display md: %s" % result.fail_reason)
573
      return None
574
    retval = {}
575
    for line in result.stdout.splitlines():
576
      line = line.strip()
577
      kv = line.split(" : ", 1)
578
      if kv:
579
        if kv[0] == "UUID":
580
          retval["uuid"] = kv[1].split()[0]
581
        elif kv[0] == "State":
582
          retval["state"] = kv[1].split(", ")
583
    return retval
584

    
585
  @staticmethod
586
  def _FindUnusedMinor():
587
    """Compute an unused MD minor.
588

589
    This code assumes that there are 256 minors only.
590

591
    """
592
    used_md = MDRaid1._GetUsedDevs()
593
    i = 0
594
    while i < 256:
595
      if i not in used_md:
596
        break
597
      i += 1
598
    if i == 256:
599
      logger.Error("Critical: Out of md minor numbers.")
600
      raise errors.BlockDeviceError("Can't find a free MD minor")
601
    return i
602

    
603
  @classmethod
604
  def _FindMDByUUID(cls, uuid):
605
    """Find the minor of an MD array with a given UUID.
606

607
    """
608
    md_list = cls._GetUsedDevs()
609
    for minor in md_list:
610
      info = cls._GetDevInfo(minor)
611
      if info and info["uuid"] == uuid:
612
        return minor
613
    return None
614

    
615
  @staticmethod
616
  def _ZeroSuperblock(dev_path):
617
    """Zero the possible locations for an MD superblock.
618

619
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
620
    fails in versions 2.x with the same error code as non-writable
621
    device.
622

623
    The superblocks are located at (negative values are relative to
624
    the end of the block device):
625
      - -128k to end for version 0.90 superblock
626
      - -8k to -12k for version 1.0 superblock (included in the above)
627
      - 0k to 4k for version 1.1 superblock
628
      - 4k to 8k for version 1.2 superblock
629

630
    To cover all situations, the zero-ing will be:
631
      - 0k to 128k
632
      - -128k to end
633

634
    As such, the minimum device size must be 128k, otherwise we'll get
635
    I/O errors.
636

637
    Note that this function depends on the fact that one can open,
638
    read and write block devices normally.
639

640
    """
641
    overwrite_size = 128 * 1024
642
    empty_buf = '\0' * overwrite_size
643
    fd = open(dev_path, "r+")
644
    try:
645
      fd.seek(0, 0)
646
      p1 = fd.tell()
647
      fd.write(empty_buf)
648
      p2 = fd.tell()
649
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
650
      fd.seek(-overwrite_size, 2)
651
      p1 = fd.tell()
652
      fd.write(empty_buf)
653
      p2 = fd.tell()
654
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
655
    finally:
656
      fd.close()
657

    
658
  @classmethod
659
  def Create(cls, unique_id, children, size):
660
    """Create a new MD raid1 array.
661

662
    """
663
    if not isinstance(children, (tuple, list)):
664
      raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
665
                       str(children))
666
    for i in children:
667
      if not isinstance(i, BlockDev):
668
        raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
669
    for i in children:
670
      try:
671
        cls._ZeroSuperblock(i.dev_path)
672
      except EnvironmentError, err:
673
        logger.Error("Can't zero superblock for %s: %s" %
674
                     (i.dev_path, str(err)))
675
        return None
676
    minor = cls._FindUnusedMinor()
677
    result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
678
                           "--auto=yes", "--force", "-l1",
679
                           "-n%d" % len(children)] +
680
                          [dev.dev_path for dev in children])
681

    
682
    if result.failed:
683
      logger.Error("Can't create md: %s: %s" % (result.fail_reason,
684
                                                result.output))
685
      return None
686
    info = cls._GetDevInfo(minor)
687
    if not info or not "uuid" in info:
688
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
689
      return None
690
    return MDRaid1(info["uuid"], children)
691

    
692
  def Remove(self):
693
    """Stub remove function for MD RAID 1 arrays.
694

695
    We don't remove the superblock right now. Mark a to do.
696

697
    """
698
    #TODO: maybe zero superblock on child devices?
699
    return self.Shutdown()
700

    
701
  def Rename(self, new_id):
702
    """Rename a device.
703

704
    This is not supported for md raid1 devices.
705

706
    """
707
    raise errors.ProgrammerError("Can't rename a md raid1 device")
708

    
709
  def AddChildren(self, devices):
710
    """Add new member(s) to the md raid1.
711

712
    """
713
    if self.minor is None and not self.Attach():
714
      raise errors.BlockDeviceError("Can't attach to device")
715

    
716
    args = ["mdadm", "-a", self.dev_path]
717
    for dev in devices:
718
      if dev.dev_path is None:
719
        raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
720
      dev.Open()
721
      args.append(dev.dev_path)
722
    result = utils.RunCmd(args)
723
    if result.failed:
724
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
725
                                    result.output)
726
    new_len = len(self._children) + len(devices)
727
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
728
    if result.failed:
729
      raise errors.BlockDeviceError("Can't grow md array: %s" %
730
                                    result.output)
731
    self._children.extend(devices)
732

    
733
  def RemoveChildren(self, devices):
734
    """Remove member(s) from the md raid1.
735

736
    """
737
    if self.minor is None and not self.Attach():
738
      raise errors.BlockDeviceError("Can't attach to device")
739
    new_len = len(self._children) - len(devices)
740
    if new_len < 1:
741
      raise errors.BlockDeviceError("Can't reduce to less than one child")
742
    args = ["mdadm", "-f", self.dev_path]
743
    orig_devs = []
744
    for dev in devices:
745
      args.append(dev.dev_path)
746
      for c in self._children:
747
        if c.dev_path == dev.dev_path:
748
          orig_devs.append(c)
749
          break
750
      else:
751
        raise errors.BlockDeviceError("Can't find device '%s' for removal" %
752
                                      dev)
753
    result = utils.RunCmd(args)
754
    if result.failed:
755
      raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
756
                                    result.output)
757

    
758
    # it seems here we need a short delay for MD to update its
759
    # superblocks
760
    time.sleep(0.5)
761
    args[1] = "-r"
762
    result = utils.RunCmd(args)
763
    if result.failed:
764
      raise errors.BlockDeviceError("Failed to remove device(s) from array:"
765
                                    " %s" % result.output)
766
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
767
                           "-n", new_len])
768
    if result.failed:
769
      raise errors.BlockDeviceError("Can't shrink md array: %s" %
770
                                    result.output)
771
    for dev in orig_devs:
772
      self._children.remove(dev)
773

    
774
  def GetStatus(self):
775
    """Return the status of the device.
776

777
    """
778
    self.Attach()
779
    if self.minor is None:
780
      retval = self.STATUS_UNKNOWN
781
    else:
782
      retval = self.STATUS_ONLINE
783
    return retval
784

    
785
  def _SetFromMinor(self, minor):
786
    """Set our parameters based on the given minor.
787

788
    This sets our minor variable and our dev_path.
789

790
    """
791
    self.minor = minor
792
    self.dev_path = "/dev/md%d" % minor
793

    
794
  def Assemble(self):
795
    """Assemble the MD device.
796

797
    At this point we should have:
798
      - list of children devices
799
      - uuid
800

801
    """
802
    result = super(MDRaid1, self).Assemble()
803
    if not result:
804
      return result
805
    md_list = self._GetUsedDevs()
806
    for minor in md_list:
807
      info = self._GetDevInfo(minor)
808
      if info and info["uuid"] == self.unique_id:
809
        self._SetFromMinor(minor)
810
        logger.Info("MD array %s already started" % str(self))
811
        return True
812
    free_minor = self._FindUnusedMinor()
813
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
814
                           self.unique_id, "/dev/md%d" % free_minor] +
815
                          [bdev.dev_path for bdev in self._children])
816
    if result.failed:
817
      logger.Error("Can't assemble MD array: %s: %s" %
818
                   (result.fail_reason, result.output))
819
      self.minor = None
820
    else:
821
      self.minor = free_minor
822
    return not result.failed
823

    
824
  def Shutdown(self):
825
    """Tear down the MD array.
826

827
    This does a 'mdadm --stop' so after this command, the array is no
828
    longer available.
829

830
    """
831
    if self.minor is None and not self.Attach():
832
      logger.Info("MD object not attached to a device")
833
      return True
834

    
835
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
836
    if result.failed:
837
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
838
      return False
839
    self.minor = None
840
    self.dev_path = None
841
    return True
842

    
843
  def SetSyncSpeed(self, kbytes):
844
    """Set the maximum sync speed for the MD array.
845

846
    """
847
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
848
    if self.minor is None:
849
      logger.Error("MD array not attached to a device")
850
      return False
851
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
852
    try:
853
      f.write("%d" % kbytes)
854
    finally:
855
      f.close()
856
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
857
    try:
858
      f.write("%d" % (kbytes/2))
859
    finally:
860
      f.close()
861
    return result
862

    
863
  def GetSyncStatus(self):
864
    """Returns the sync status of the device.
865

866
    Returns:
867
     (sync_percent, estimated_time)
868

869
    If sync_percent is None, it means all is ok
870
    If estimated_time is None, it means we can't esimate
871
    the time needed, otherwise it's the time left in seconds
872

873
    """
874
    if self.minor is None and not self.Attach():
875
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
876
    dev_info = self._GetDevInfo(self.minor)
877
    is_clean = ("state" in dev_info and
878
                len(dev_info["state"]) == 1 and
879
                dev_info["state"][0] in ("clean", "active"))
880
    sys_path = "/sys/block/md%s/md/" % self.minor
881
    f = file(sys_path + "sync_action")
882
    sync_status = f.readline().strip()
883
    f.close()
884
    if sync_status == "idle":
885
      return None, None, not is_clean
886
    f = file(sys_path + "sync_completed")
887
    sync_completed = f.readline().strip().split(" / ")
888
    f.close()
889
    if len(sync_completed) != 2:
890
      return 0, None, not is_clean
891
    sync_done, sync_total = [float(i) for i in sync_completed]
892
    sync_percent = 100.0*sync_done/sync_total
893
    f = file(sys_path + "sync_speed")
894
    sync_speed_k = int(f.readline().strip())
895
    if sync_speed_k == 0:
896
      time_est = None
897
    else:
898
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
899
    return sync_percent, time_est, not is_clean
900

    
901
  def Open(self, force=False):
902
    """Make the device ready for I/O.
903

904
    This is a no-op for the MDRaid1 device type, although we could use
905
    the 2.6.18's new array_state thing.
906

907
    """
908
    return True
909

    
910
  def Close(self):
911
    """Notifies that the device will no longer be used for I/O.
912

913
    This is a no-op for the MDRaid1 device type, but see comment for
914
    `Open()`.
915

916
    """
917
    return True
918

    
919

    
920
class BaseDRBD(BlockDev):
921
  """Base DRBD class.
922

923
  This class contains a few bits of common functionality between the
924
  0.7 and 8.x versions of DRBD.
925

926
  """
927
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
928
                           r" \(api:(\d+)/proto:(\d+)\)")
929
  _DRBD_MAJOR = 147
930
  _ST_UNCONFIGURED = "Unconfigured"
931
  _ST_WFCONNECTION = "WFConnection"
932
  _ST_CONNECTED = "Connected"
933

    
934
  @staticmethod
935
  def _GetProcData():
936
    """Return data from /proc/drbd.
937

938
    """
939
    stat = open("/proc/drbd", "r")
940
    try:
941
      data = stat.read().splitlines()
942
    finally:
943
      stat.close()
944
    if not data:
945
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
946
    return data
947

    
948
  @staticmethod
949
  def _MassageProcData(data):
950
    """Transform the output of _GetProdData into a nicer form.
951

952
    Returns:
953
      a dictionary of minor: joined lines from /proc/drbd for that minor
954

955
    """
956
    lmatch = re.compile("^ *([0-9]+):.*$")
957
    results = {}
958
    old_minor = old_line = None
959
    for line in data:
960
      lresult = lmatch.match(line)
961
      if lresult is not None:
962
        if old_minor is not None:
963
          results[old_minor] = old_line
964
        old_minor = int(lresult.group(1))
965
        old_line = line
966
      else:
967
        if old_minor is not None:
968
          old_line += " " + line.strip()
969
    # add last line
970
    if old_minor is not None:
971
      results[old_minor] = old_line
972
    return results
973

    
974
  @classmethod
975
  def _GetVersion(cls):
976
    """Return the DRBD version.
977

978
    This will return a list [k_major, k_minor, k_point, api, proto].
979

980
    """
981
    proc_data = cls._GetProcData()
982
    first_line = proc_data[0].strip()
983
    version = cls._VERSION_RE.match(first_line)
984
    if not version:
985
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
986
                                    first_line)
987
    return [int(val) for val in version.groups()]
988

    
989
  @staticmethod
990
  def _DevPath(minor):
991
    """Return the path to a drbd device for a given minor.
992

993
    """
994
    return "/dev/drbd%d" % minor
995

    
996
  @classmethod
997
  def _GetUsedDevs(cls):
998
    """Compute the list of used DRBD devices.
999

1000
    """
1001
    data = cls._GetProcData()
1002

    
1003
    used_devs = {}
1004
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1005
    for line in data:
1006
      match = valid_line.match(line)
1007
      if not match:
1008
        continue
1009
      minor = int(match.group(1))
1010
      state = match.group(2)
1011
      if state == cls._ST_UNCONFIGURED:
1012
        continue
1013
      used_devs[minor] = state, line
1014

    
1015
    return used_devs
1016

    
1017
  def _SetFromMinor(self, minor):
1018
    """Set our parameters based on the given minor.
1019

1020
    This sets our minor variable and our dev_path.
1021

1022
    """
1023
    if minor is None:
1024
      self.minor = self.dev_path = None
1025
    else:
1026
      self.minor = minor
1027
      self.dev_path = self._DevPath(minor)
1028

    
1029
  @staticmethod
1030
  def _CheckMetaSize(meta_device):
1031
    """Check if the given meta device looks like a valid one.
1032

1033
    This currently only check the size, which must be around
1034
    128MiB.
1035

1036
    """
1037
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1038
    if result.failed:
1039
      logger.Error("Failed to get device size: %s" % result.fail_reason)
1040
      return False
1041
    try:
1042
      sectors = int(result.stdout)
1043
    except ValueError:
1044
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1045
      return False
1046
    bytes = sectors * 512
1047
    if bytes < 128 * 1024 * 1024: # less than 128MiB
1048
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1049
      return False
1050
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1051
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1052
      return False
1053
    return True
1054

    
1055
  def Rename(self, new_id):
1056
    """Rename a device.
1057

1058
    This is not supported for drbd devices.
1059

1060
    """
1061
    raise errors.ProgrammerError("Can't rename a drbd device")
1062

    
1063

    
1064
class DRBDev(BaseDRBD):
1065
  """DRBD block device.
1066

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

1071
  The unique_id for the drbd device is the (local_ip, local_port,
1072
  remote_ip, remote_port) tuple, and it must have two children: the
1073
  data device and the meta_device. The meta device is checked for
1074
  valid size and is zeroed on create.
1075

1076
  """
1077
  def __init__(self, unique_id, children):
1078
    super(DRBDev, self).__init__(unique_id, children)
1079
    self.major = self._DRBD_MAJOR
1080
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1081
    if kmaj != 0 and kmin != 7:
1082
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1083
                                    " requested ganeti usage: kernel is"
1084
                                    " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1085

    
1086
    if len(children) != 2:
1087
      raise ValueError("Invalid configuration data %s" % str(children))
1088
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1089
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1090
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1091
    self.Attach()
1092

    
1093
  @classmethod
1094
  def _FindUnusedMinor(cls):
1095
    """Find an unused DRBD device.
1096

1097
    """
1098
    data = cls._GetProcData()
1099

    
1100
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1101
    for line in data:
1102
      match = valid_line.match(line)
1103
      if match:
1104
        return int(match.group(1))
1105
    logger.Error("Error: no free drbd minors!")
1106
    raise errors.BlockDeviceError("Can't find a free DRBD minor")
1107

    
1108
  @classmethod
1109
  def _GetDevInfo(cls, minor):
1110
    """Get details about a given DRBD minor.
1111

1112
    This return, if available, the local backing device in (major,
1113
    minor) formant and the local and remote (ip, port) information.
1114

1115
    """
1116
    data = {}
1117
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1118
    if result.failed:
1119
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1120
      return data
1121
    out = result.stdout
1122
    if out == "Not configured\n":
1123
      return data
1124
    for line in out.splitlines():
1125
      if "local_dev" not in data:
1126
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1127
        if match:
1128
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1129
          continue
1130
      if "meta_dev" not in data:
1131
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1132
        if match:
1133
          if match.group(2) is not None and match.group(3) is not None:
1134
            # matched on the major/minor
1135
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1136
          else:
1137
            # matched on the "internal" string
1138
            data["meta_dev"] = match.group(1)
1139
            # in this case, no meta_index is in the output
1140
            data["meta_index"] = -1
1141
          continue
1142
      if "meta_index" not in data:
1143
        match = re.match("^Meta index: ([0-9]+).*$", line)
1144
        if match:
1145
          data["meta_index"] = int(match.group(1))
1146
          continue
1147
      if "local_addr" not in data:
1148
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1149
        if match:
1150
          data["local_addr"] = (match.group(1), int(match.group(2)))
1151
          continue
1152
      if "remote_addr" not in data:
1153
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1154
        if match:
1155
          data["remote_addr"] = (match.group(1), int(match.group(2)))
1156
          continue
1157
    return data
1158

    
1159
  def _MatchesLocal(self, info):
1160
    """Test if our local config matches with an existing device.
1161

1162
    The parameter should be as returned from `_GetDevInfo()`. This
1163
    method tests if our local backing device is the same as the one in
1164
    the info parameter, in effect testing if we look like the given
1165
    device.
1166

1167
    """
1168
    if not ("local_dev" in info and "meta_dev" in info and
1169
            "meta_index" in info):
1170
      return False
1171

    
1172
    backend = self._children[0]
1173
    if backend is not None:
1174
      retval = (info["local_dev"] == (backend.major, backend.minor))
1175
    else:
1176
      retval = (info["local_dev"] == (0, 0))
1177
    meta = self._children[1]
1178
    if meta is not None:
1179
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1180
      retval = retval and (info["meta_index"] == 0)
1181
    else:
1182
      retval = retval and (info["meta_dev"] == "internal" and
1183
                           info["meta_index"] == -1)
1184
    return retval
1185

    
1186
  def _MatchesNet(self, info):
1187
    """Test if our network config matches with an existing device.
1188

1189
    The parameter should be as returned from `_GetDevInfo()`. This
1190
    method tests if our network configuration is the same as the one
1191
    in the info parameter, in effect testing if we look like the given
1192
    device.
1193

1194
    """
1195
    if (((self._lhost is None and not ("local_addr" in info)) and
1196
         (self._rhost is None and not ("remote_addr" in info)))):
1197
      return True
1198

    
1199
    if self._lhost is None:
1200
      return False
1201

    
1202
    if not ("local_addr" in info and
1203
            "remote_addr" in info):
1204
      return False
1205

    
1206
    retval = (info["local_addr"] == (self._lhost, self._lport))
1207
    retval = (retval and
1208
              info["remote_addr"] == (self._rhost, self._rport))
1209
    return retval
1210

    
1211
  @classmethod
1212
  def _AssembleLocal(cls, minor, backend, meta):
1213
    """Configure the local part of a DRBD device.
1214

1215
    This is the first thing that must be done on an unconfigured DRBD
1216
    device. And it must be done only once.
1217

1218
    """
1219
    if not cls._CheckMetaSize(meta):
1220
      return False
1221
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1222
                           backend, meta, "0", "-e", "detach"])
1223
    if result.failed:
1224
      logger.Error("Can't attach local disk: %s" % result.output)
1225
    return not result.failed
1226

    
1227
  @classmethod
1228
  def _ShutdownLocal(cls, minor):
1229
    """Detach from the local device.
1230

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

1234
    """
1235
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1236
    if result.failed:
1237
      logger.Error("Can't detach local device: %s" % result.output)
1238
    return not result.failed
1239

    
1240
  @staticmethod
1241
  def _ShutdownAll(minor):
1242
    """Deactivate the device.
1243

1244
    This will, of course, fail if the device is in use.
1245

1246
    """
1247
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1248
    if result.failed:
1249
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1250
    return not result.failed
1251

    
1252
  @classmethod
1253
  def _AssembleNet(cls, minor, net_info, protocol):
1254
    """Configure the network part of the device.
1255

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

1263
    """
1264
    lhost, lport, rhost, rport = net_info
1265
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1266
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1267
                           protocol])
1268
    if result.failed:
1269
      logger.Error("Can't setup network for dbrd device: %s" %
1270
                   result.fail_reason)
1271
      return False
1272

    
1273
    timeout = time.time() + 10
1274
    ok = False
1275
    while time.time() < timeout:
1276
      info = cls._GetDevInfo(minor)
1277
      if not "local_addr" in info or not "remote_addr" in info:
1278
        time.sleep(1)
1279
        continue
1280
      if (info["local_addr"] != (lhost, lport) or
1281
          info["remote_addr"] != (rhost, rport)):
1282
        time.sleep(1)
1283
        continue
1284
      ok = True
1285
      break
1286
    if not ok:
1287
      logger.Error("Timeout while configuring network")
1288
      return False
1289
    return True
1290

    
1291
  @classmethod
1292
  def _ShutdownNet(cls, minor):
1293
    """Disconnect from the remote peer.
1294

1295
    This fails if we don't have a local device.
1296

1297
    """
1298
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1299
    logger.Error("Can't shutdown network: %s" % result.output)
1300
    return not result.failed
1301

    
1302
  def Assemble(self):
1303
    """Assemble the drbd.
1304

1305
    Method:
1306
      - if we have a local backing device, we bind to it by:
1307
        - checking the list of used drbd devices
1308
        - check if the local minor use of any of them is our own device
1309
        - if yes, abort?
1310
        - if not, bind
1311
      - if we have a local/remote net info:
1312
        - redo the local backing device step for the remote device
1313
        - check if any drbd device is using the local port,
1314
          if yes abort
1315
        - check if any remote drbd device is using the remote
1316
          port, if yes abort (for now)
1317
        - bind our net port
1318
        - bind the remote net port
1319

1320
    """
1321
    self.Attach()
1322
    if self.minor is not None:
1323
      logger.Info("Already assembled")
1324
      return True
1325

    
1326
    result = super(DRBDev, self).Assemble()
1327
    if not result:
1328
      return result
1329

    
1330
    minor = self._FindUnusedMinor()
1331
    need_localdev_teardown = False
1332
    if self._children[0]:
1333
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1334
                                   self._children[1].dev_path)
1335
      if not result:
1336
        return False
1337
      need_localdev_teardown = True
1338
    if self._lhost and self._lport and self._rhost and self._rport:
1339
      result = self._AssembleNet(minor,
1340
                                 (self._lhost, self._lport,
1341
                                  self._rhost, self._rport),
1342
                                 "C")
1343
      if not result:
1344
        if need_localdev_teardown:
1345
          # we will ignore failures from this
1346
          logger.Error("net setup failed, tearing down local device")
1347
          self._ShutdownAll(minor)
1348
        return False
1349
    self._SetFromMinor(minor)
1350
    return True
1351

    
1352
  def Shutdown(self):
1353
    """Shutdown the DRBD device.
1354

1355
    """
1356
    if self.minor is None and not self.Attach():
1357
      logger.Info("DRBD device not attached to a device during Shutdown")
1358
      return True
1359
    if not self._ShutdownAll(self.minor):
1360
      return False
1361
    self.minor = None
1362
    self.dev_path = None
1363
    return True
1364

    
1365
  def Attach(self):
1366
    """Find a DRBD device which matches our config and attach to it.
1367

1368
    In case of partially attached (local device matches but no network
1369
    setup), we perform the network attach. If successful, we re-test
1370
    the attach if can return success.
1371

1372
    """
1373
    for minor in self._GetUsedDevs():
1374
      info = self._GetDevInfo(minor)
1375
      match_l = self._MatchesLocal(info)
1376
      match_r = self._MatchesNet(info)
1377
      if match_l and match_r:
1378
        break
1379
      if match_l and not match_r and "local_addr" not in info:
1380
        res_r = self._AssembleNet(minor,
1381
                                  (self._lhost, self._lport,
1382
                                   self._rhost, self._rport),
1383
                                  "C")
1384
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1385
          break
1386
    else:
1387
      minor = None
1388

    
1389
    self._SetFromMinor(minor)
1390
    return minor is not None
1391

    
1392
  def Open(self, force=False):
1393
    """Make the local state primary.
1394

1395
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1396
    is given. Since this is a pottentialy dangerous operation, the
1397
    force flag should be only given after creation, when it actually
1398
    has to be given.
1399

1400
    """
1401
    if self.minor is None and not self.Attach():
1402
      logger.Error("DRBD cannot attach to a device during open")
1403
      return False
1404
    cmd = ["drbdsetup", self.dev_path, "primary"]
1405
    if force:
1406
      cmd.append("--do-what-I-say")
1407
    result = utils.RunCmd(cmd)
1408
    if result.failed:
1409
      logger.Error("Can't make drbd device primary: %s" % result.output)
1410
      return False
1411
    return True
1412

    
1413
  def Close(self):
1414
    """Make the local state secondary.
1415

1416
    This will, of course, fail if the device is in use.
1417

1418
    """
1419
    if self.minor is None and not self.Attach():
1420
      logger.Info("Instance not attached to a device")
1421
      raise errors.BlockDeviceError("Can't find device")
1422
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1423
    if result.failed:
1424
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1425
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1426

    
1427
  def SetSyncSpeed(self, kbytes):
1428
    """Set the speed of the DRBD syncer.
1429

1430
    """
1431
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1432
    if self.minor is None:
1433
      logger.Info("Instance not attached to a device")
1434
      return False
1435
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1436
                           kbytes])
1437
    if result.failed:
1438
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1439
    return not result.failed and children_result
1440

    
1441
  def GetSyncStatus(self):
1442
    """Returns the sync status of the device.
1443

1444
    Returns:
1445
     (sync_percent, estimated_time)
1446

1447
    If sync_percent is None, it means all is ok
1448
    If estimated_time is None, it means we can't esimate
1449
    the time needed, otherwise it's the time left in seconds
1450

1451
    """
1452
    if self.minor is None and not self.Attach():
1453
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1454
    proc_info = self._MassageProcData(self._GetProcData())
1455
    if self.minor not in proc_info:
1456
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1457
                                    self.minor)
1458
    line = proc_info[self.minor]
1459
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1460
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1461
    if match:
1462
      sync_percent = float(match.group(1))
1463
      hours = int(match.group(2))
1464
      minutes = int(match.group(3))
1465
      seconds = int(match.group(4))
1466
      est_time = hours * 3600 + minutes * 60 + seconds
1467
    else:
1468
      sync_percent = None
1469
      est_time = None
1470
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1471
    if not match:
1472
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1473
                                    self.minor)
1474
    client_state = match.group(1)
1475
    is_degraded = client_state != "Connected"
1476
    return sync_percent, est_time, is_degraded
1477

    
1478
  def GetStatus(self):
1479
    """Compute the status of the DRBD device
1480

1481
    Note that DRBD devices don't have the STATUS_EXISTING state.
1482

1483
    """
1484
    if self.minor is None and not self.Attach():
1485
      return self.STATUS_UNKNOWN
1486

    
1487
    data = self._GetProcData()
1488
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1489
                       self.minor)
1490
    for line in data:
1491
      mresult = match.match(line)
1492
      if mresult:
1493
        break
1494
    else:
1495
      logger.Error("Can't find myself!")
1496
      return self.STATUS_UNKNOWN
1497

    
1498
    state = mresult.group(2)
1499
    if state == "Primary":
1500
      result = self.STATUS_ONLINE
1501
    else:
1502
      result = self.STATUS_STANDBY
1503

    
1504
    return result
1505

    
1506
  @staticmethod
1507
  def _ZeroDevice(device):
1508
    """Zero a device.
1509

1510
    This writes until we get ENOSPC.
1511

1512
    """
1513
    f = open(device, "w")
1514
    buf = "\0" * 1048576
1515
    try:
1516
      while True:
1517
        f.write(buf)
1518
    except IOError, err:
1519
      if err.errno != errno.ENOSPC:
1520
        raise
1521

    
1522
  @classmethod
1523
  def Create(cls, unique_id, children, size):
1524
    """Create a new DRBD device.
1525

1526
    Since DRBD devices are not created per se, just assembled, this
1527
    function just zeroes the meta device.
1528

1529
    """
1530
    if len(children) != 2:
1531
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1532
    meta = children[1]
1533
    meta.Assemble()
1534
    if not meta.Attach():
1535
      raise errors.BlockDeviceError("Can't attach to meta device")
1536
    if not cls._CheckMetaSize(meta.dev_path):
1537
      raise errors.BlockDeviceError("Invalid meta device")
1538
    logger.Info("Started zeroing device %s" % meta.dev_path)
1539
    cls._ZeroDevice(meta.dev_path)
1540
    logger.Info("Done zeroing device %s" % meta.dev_path)
1541
    return cls(unique_id, children)
1542

    
1543
  def Remove(self):
1544
    """Stub remove for DRBD devices.
1545

1546
    """
1547
    return self.Shutdown()
1548

    
1549

    
1550
class DRBD8(BaseDRBD):
1551
  """DRBD v8.x block device.
1552

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

1557
  The unique_id for the drbd device is the (local_ip, local_port,
1558
  remote_ip, remote_port) tuple, and it must have two children: the
1559
  data device and the meta_device. The meta device is checked for
1560
  valid size and is zeroed on create.
1561

1562
  """
1563
  _MAX_MINORS = 255
1564
  _PARSE_SHOW = None
1565

    
1566
  def __init__(self, unique_id, children):
1567
    super(DRBD8, self).__init__(unique_id, children)
1568
    self.major = self._DRBD_MAJOR
1569
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1570
    if kmaj != 8:
1571
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1572
                                    " requested ganeti usage: kernel is"
1573
                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1574

    
1575
    if len(children) not in (0, 2):
1576
      raise ValueError("Invalid configuration data %s" % str(children))
1577
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1578
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1579
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1580
    self.Attach()
1581

    
1582
  @classmethod
1583
  def _InitMeta(cls, minor, dev_path):
1584
    """Initialize a meta device.
1585

1586
    This will not work if the given minor is in use.
1587

1588
    """
1589
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1590
                           "v08", dev_path, "0", "create-md"])
1591
    if result.failed:
1592
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1593
                                    result.output)
1594

    
1595
  @classmethod
1596
  def _FindUnusedMinor(cls):
1597
    """Find an unused DRBD device.
1598

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

1602
    """
1603
    data = cls._GetProcData()
1604

    
1605
    unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1606
    used_line = re.compile("^ *([0-9]+): cs:")
1607
    highest = None
1608
    for line in data:
1609
      match = unused_line.match(line)
1610
      if match:
1611
        return int(match.group(1))
1612
      match = used_line.match(line)
1613
      if match:
1614
        minor = int(match.group(1))
1615
        highest = max(highest, minor)
1616
    if highest is None: # there are no minors in use at all
1617
      return 0
1618
    if highest >= cls._MAX_MINORS:
1619
      logger.Error("Error: no free drbd minors!")
1620
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1621
    return highest + 1
1622

    
1623
  @classmethod
1624
  def _IsValidMeta(cls, meta_device):
1625
    """Check if the given meta device looks like a valid one.
1626

1627
    """
1628
    minor = cls._FindUnusedMinor()
1629
    minor_path = cls._DevPath(minor)
1630
    result = utils.RunCmd(["drbdmeta", minor_path,
1631
                           "v08", meta_device, "0",
1632
                           "dstate"])
1633
    if result.failed:
1634
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1635
      return False
1636
    return True
1637

    
1638
  @classmethod
1639
  def _GetShowParser(cls):
1640
    """Return a parser for `drbd show` output.
1641

1642
    This will either create or return an already-create parser for the
1643
    output of the command `drbd show`.
1644

1645
    """
1646
    if cls._PARSE_SHOW is not None:
1647
      return cls._PARSE_SHOW
1648

    
1649
    # pyparsing setup
1650
    lbrace = pyp.Literal("{").suppress()
1651
    rbrace = pyp.Literal("}").suppress()
1652
    semi = pyp.Literal(";").suppress()
1653
    # this also converts the value to an int
1654
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t:(l, [int(t[0])]))
1655

    
1656
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1657
    defa = pyp.Literal("_is_default").suppress()
1658
    dbl_quote = pyp.Literal('"').suppress()
1659

    
1660
    keyword = pyp.Word(pyp.alphanums + '-')
1661

    
1662
    # value types
1663
    value = pyp.Word(pyp.alphanums + '_-/.:')
1664
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1665
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1666
                 number)
1667
    # meta device, extended syntax
1668
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1669
                  number + pyp.Word(']').suppress())
1670

    
1671
    # a statement
1672
    stmt = (~rbrace + keyword + ~lbrace +
1673
            (addr_port ^ value ^ quoted ^ meta_value) +
1674
            pyp.Optional(defa) + semi +
1675
            pyp.Optional(pyp.restOfLine).suppress())
1676

    
1677
    # an entire section
1678
    section_name = pyp.Word(pyp.alphas + '_')
1679
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1680

    
1681
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1682
    bnf.ignore(comment)
1683

    
1684
    cls._PARSE_SHOW = bnf
1685

    
1686
    return bnf
1687

    
1688
  @classmethod
1689
  def _GetDevInfo(cls, minor):
1690
    """Get details about a given DRBD minor.
1691

1692
    This return, if available, the local backing device (as a path)
1693
    and the local and remote (ip, port) information.
1694

1695
    """
1696
    data = {}
1697
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1698
    if result.failed:
1699
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1700
      return data
1701
    out = result.stdout
1702
    if not out:
1703
      return data
1704

    
1705
    bnf = cls._GetShowParser()
1706
    # run pyparse
1707

    
1708
    try:
1709
      results = bnf.parseString(out)
1710
    except pyp.ParseException, err:
1711
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1712
                                    str(err))
1713

    
1714
    # and massage the results into our desired format
1715
    for section in results:
1716
      sname = section[0]
1717
      if sname == "_this_host":
1718
        for lst in section[1:]:
1719
          if lst[0] == "disk":
1720
            data["local_dev"] = lst[1]
1721
          elif lst[0] == "meta-disk":
1722
            data["meta_dev"] = lst[1]
1723
            data["meta_index"] = lst[2]
1724
          elif lst[0] == "address":
1725
            data["local_addr"] = tuple(lst[1:])
1726
      elif sname == "_remote_host":
1727
        for lst in section[1:]:
1728
          if lst[0] == "address":
1729
            data["remote_addr"] = tuple(lst[1:])
1730
    return data
1731

    
1732
  def _MatchesLocal(self, info):
1733
    """Test if our local config matches with an existing device.
1734

1735
    The parameter should be as returned from `_GetDevInfo()`. This
1736
    method tests if our local backing device is the same as the one in
1737
    the info parameter, in effect testing if we look like the given
1738
    device.
1739

1740
    """
1741
    if self._children:
1742
      backend, meta = self._children
1743
    else:
1744
      backend = meta = None
1745

    
1746
    if backend is not None:
1747
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1748
    else:
1749
      retval = ("local_dev" not in info)
1750

    
1751
    if meta is not None:
1752
      retval = retval and ("meta_dev" in info and
1753
                           info["meta_dev"] == meta.dev_path)
1754
      retval = retval and ("meta_index" in info and
1755
                           info["meta_index"] == 0)
1756
    else:
1757
      retval = retval and ("meta_dev" not in info and
1758
                           "meta_index" not in info)
1759
    return retval
1760

    
1761
  def _MatchesNet(self, info):
1762
    """Test if our network config matches with an existing device.
1763

1764
    The parameter should be as returned from `_GetDevInfo()`. This
1765
    method tests if our network configuration is the same as the one
1766
    in the info parameter, in effect testing if we look like the given
1767
    device.
1768

1769
    """
1770
    if (((self._lhost is None and not ("local_addr" in info)) and
1771
         (self._rhost is None and not ("remote_addr" in info)))):
1772
      return True
1773

    
1774
    if self._lhost is None:
1775
      return False
1776

    
1777
    if not ("local_addr" in info and
1778
            "remote_addr" in info):
1779
      return False
1780

    
1781
    retval = (info["local_addr"] == (self._lhost, self._lport))
1782
    retval = (retval and
1783
              info["remote_addr"] == (self._rhost, self._rport))
1784
    return retval
1785

    
1786
  @classmethod
1787
  def _AssembleLocal(cls, minor, backend, meta):
1788
    """Configure the local part of a DRBD device.
1789

1790
    This is the first thing that must be done on an unconfigured DRBD
1791
    device. And it must be done only once.
1792

1793
    """
1794
    if not cls._IsValidMeta(meta):
1795
      return False
1796
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1797
                           backend, meta, "0", "-e", "detach",
1798
                           "--create-device"])
1799
    if result.failed:
1800
      logger.Error("Can't attach local disk: %s" % result.output)
1801
    return not result.failed
1802

    
1803
  @classmethod
1804
  def _AssembleNet(cls, minor, net_info, protocol,
1805
                   dual_pri=False, hmac=None, secret=None):
1806
    """Configure the network part of the device.
1807

1808
    """
1809
    lhost, lport, rhost, rport = net_info
1810
    args = ["drbdsetup", cls._DevPath(minor), "net",
1811
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1812
            "-A", "discard-zero-changes",
1813
            "-B", "consensus",
1814
            ]
1815
    if dual_pri:
1816
      args.append("-m")
1817
    if hmac and secret:
1818
      args.extend(["-a", hmac, "-x", secret])
1819
    result = utils.RunCmd(args)
1820
    if result.failed:
1821
      logger.Error("Can't setup network for dbrd device: %s" %
1822
                   result.fail_reason)
1823
      return False
1824

    
1825
    timeout = time.time() + 10
1826
    ok = False
1827
    while time.time() < timeout:
1828
      info = cls._GetDevInfo(minor)
1829
      if not "local_addr" in info or not "remote_addr" in info:
1830
        time.sleep(1)
1831
        continue
1832
      if (info["local_addr"] != (lhost, lport) or
1833
          info["remote_addr"] != (rhost, rport)):
1834
        time.sleep(1)
1835
        continue
1836
      ok = True
1837
      break
1838
    if not ok:
1839
      logger.Error("Timeout while configuring network")
1840
      return False
1841
    return True
1842

    
1843
  def AddChildren(self, devices):
1844
    """Add a disk to the DRBD device.
1845

1846
    """
1847
    if self.minor is None:
1848
      raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1849

    
1850
    if len(devices) != 2:
1851
      raise errors.BlockDeviceError("Need two devices for AddChildren")
1852
    if self._children:
1853
      raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1854
    backend, meta = devices
1855
    if backend.dev_path is None or meta.dev_path is None:
1856
      raise errors.BlockDeviceError("Children not ready during AddChildren")
1857
    backend.Open()
1858
    meta.Open()
1859
    if not self._CheckMetaSize(meta.dev_path):
1860
      raise errors.BlockDeviceError("Invalid meta device size")
1861
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1862
    if not self._IsValidMeta(meta.dev_path):
1863
      raise errors.BlockDeviceError("Cannot initalize meta device")
1864

    
1865
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1866
      raise errors.BlockDeviceError("Can't attach to local storage")
1867
    self._children = devices
1868

    
1869
  def RemoveChildren(self, devices):
1870
    """Detach the drbd device from local storage.
1871

1872
    """
1873
    if self.minor is None:
1874
      raise errors.BlockDeviceError("Can't attach to drbd8 during"
1875
                                    " RemoveChildren")
1876
    if len(self._children) != 2:
1877
      raise errors.BlockDeviceError("We don't have two children: %s" %
1878
                                    self._children)
1879

    
1880
    if len(devices) != 2:
1881
      raise errors.BlockDeviceError("We need two children in RemoveChildren")
1882
    for idx, dev in enumerate(devices):
1883
      if dev.dev_path != self._children[idx].dev_path:
1884
        raise errors.BlockDeviceError("Mismatch in local storage (%d) in"
1885
                                      " RemoveChildren" % idx)
1886

    
1887
    if not self._ShutdownLocal(self.minor):
1888
      raise errors.BlockDeviceError("Can't detach from local storage")
1889
    self._children = []
1890

    
1891
  def SetSyncSpeed(self, kbytes):
1892
    """Set the speed of the DRBD syncer.
1893

1894
    """
1895
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1896
    if self.minor is None:
1897
      logger.Info("Instance not attached to a device")
1898
      return False
1899
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1900
                           kbytes])
1901
    if result.failed:
1902
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1903
    return not result.failed and children_result
1904

    
1905
  def GetSyncStatus(self):
1906
    """Returns the sync status of the device.
1907

1908
    Returns:
1909
     (sync_percent, estimated_time)
1910

1911
    If sync_percent is None, it means all is ok
1912
    If estimated_time is None, it means we can't esimate
1913
    the time needed, otherwise it's the time left in seconds
1914

1915
    """
1916
    if self.minor is None and not self.Attach():
1917
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1918
    proc_info = self._MassageProcData(self._GetProcData())
1919
    if self.minor not in proc_info:
1920
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1921
                                    self.minor)
1922
    line = proc_info[self.minor]
1923
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1924
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1925
    if match:
1926
      sync_percent = float(match.group(1))
1927
      hours = int(match.group(2))
1928
      minutes = int(match.group(3))
1929
      seconds = int(match.group(4))
1930
      est_time = hours * 3600 + minutes * 60 + seconds
1931
    else:
1932
      sync_percent = None
1933
      est_time = None
1934
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1935
    if not match:
1936
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1937
                                    self.minor)
1938
    client_state = match.group(1)
1939
    is_degraded = client_state != "Connected"
1940
    return sync_percent, est_time, is_degraded
1941

    
1942
  def GetStatus(self):
1943
    """Compute the status of the DRBD device
1944

1945
    Note that DRBD devices don't have the STATUS_EXISTING state.
1946

1947
    """
1948
    if self.minor is None and not self.Attach():
1949
      return self.STATUS_UNKNOWN
1950

    
1951
    data = self._GetProcData()
1952
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1953
                       self.minor)
1954
    for line in data:
1955
      mresult = match.match(line)
1956
      if mresult:
1957
        break
1958
    else:
1959
      logger.Error("Can't find myself!")
1960
      return self.STATUS_UNKNOWN
1961

    
1962
    state = mresult.group(2)
1963
    if state == "Primary":
1964
      result = self.STATUS_ONLINE
1965
    else:
1966
      result = self.STATUS_STANDBY
1967

    
1968
    return result
1969

    
1970
  def Open(self, force=False):
1971
    """Make the local state primary.
1972

1973
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1974
    is given. Since this is a pottentialy dangerous operation, the
1975
    force flag should be only given after creation, when it actually
1976
    has to be given.
1977

1978
    """
1979
    if self.minor is None and not self.Attach():
1980
      logger.Error("DRBD cannot attach to a device during open")
1981
      return False
1982
    cmd = ["drbdsetup", self.dev_path, "primary"]
1983
    if force:
1984
      cmd.append("-o")
1985
    result = utils.RunCmd(cmd)
1986
    if result.failed:
1987
      logger.Error("Can't make drbd device primary: %s" % result.output)
1988
      return False
1989
    return True
1990

    
1991
  def Close(self):
1992
    """Make the local state secondary.
1993

1994
    This will, of course, fail if the device is in use.
1995

1996
    """
1997
    if self.minor is None and not self.Attach():
1998
      logger.Info("Instance not attached to a device")
1999
      raise errors.BlockDeviceError("Can't find device")
2000
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2001
    if result.failed:
2002
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
2003
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
2004

    
2005
  def Attach(self):
2006
    """Find a DRBD device which matches our config and attach to it.
2007

2008
    In case of partially attached (local device matches but no network
2009
    setup), we perform the network attach. If successful, we re-test
2010
    the attach if can return success.
2011

2012
    """
2013
    for minor in self._GetUsedDevs():
2014
      info = self._GetDevInfo(minor)
2015
      match_l = self._MatchesLocal(info)
2016
      match_r = self._MatchesNet(info)
2017
      if match_l and match_r:
2018
        break
2019
      if match_l and not match_r and "local_addr" not in info:
2020
        res_r = self._AssembleNet(minor,
2021
                                  (self._lhost, self._lport,
2022
                                   self._rhost, self._rport),
2023
                                  "C")
2024
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
2025
          break
2026
    else:
2027
      minor = None
2028

    
2029
    self._SetFromMinor(minor)
2030
    return minor is not None
2031

    
2032
  def Assemble(self):
2033
    """Assemble the drbd.
2034

2035
    Method:
2036
      - if we have a local backing device, we bind to it by:
2037
        - checking the list of used drbd devices
2038
        - check if the local minor use of any of them is our own device
2039
        - if yes, abort?
2040
        - if not, bind
2041
      - if we have a local/remote net info:
2042
        - redo the local backing device step for the remote device
2043
        - check if any drbd device is using the local port,
2044
          if yes abort
2045
        - check if any remote drbd device is using the remote
2046
          port, if yes abort (for now)
2047
        - bind our net port
2048
        - bind the remote net port
2049

2050
    """
2051
    self.Attach()
2052
    if self.minor is not None:
2053
      logger.Info("Already assembled")
2054
      return True
2055

    
2056
    result = super(DRBD8, self).Assemble()
2057
    if not result:
2058
      return result
2059

    
2060
    minor = self._FindUnusedMinor()
2061
    need_localdev_teardown = False
2062
    if self._children[0]:
2063
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2064
                                   self._children[1].dev_path)
2065
      if not result:
2066
        return False
2067
      need_localdev_teardown = True
2068
    if self._lhost and self._lport and self._rhost and self._rport:
2069
      result = self._AssembleNet(minor,
2070
                                 (self._lhost, self._lport,
2071
                                  self._rhost, self._rport),
2072
                                 "C")
2073
      if not result:
2074
        if need_localdev_teardown:
2075
          # we will ignore failures from this
2076
          logger.Error("net setup failed, tearing down local device")
2077
          self._ShutdownAll(minor)
2078
        return False
2079
    self._SetFromMinor(minor)
2080
    return True
2081

    
2082
  @classmethod
2083
  def _ShutdownLocal(cls, minor):
2084
    """Detach from the local device.
2085

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

2089
    """
2090
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2091
    if result.failed:
2092
      logger.Error("Can't detach local device: %s" % result.output)
2093
    return not result.failed
2094

    
2095
  @classmethod
2096
  def _ShutdownNet(cls, minor):
2097
    """Disconnect from the remote peer.
2098

2099
    This fails if we don't have a local device.
2100

2101
    """
2102
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2103
    logger.Error("Can't shutdown network: %s" % result.output)
2104
    return not result.failed
2105

    
2106
  @classmethod
2107
  def _ShutdownAll(cls, minor):
2108
    """Deactivate the device.
2109

2110
    This will, of course, fail if the device is in use.
2111

2112
    """
2113
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2114
    if result.failed:
2115
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2116
    return not result.failed
2117

    
2118
  def Shutdown(self):
2119
    """Shutdown the DRBD device.
2120

2121
    """
2122
    if self.minor is None and not self.Attach():
2123
      logger.Info("DRBD device not attached to a device during Shutdown")
2124
      return True
2125
    if not self._ShutdownAll(self.minor):
2126
      return False
2127
    self.minor = None
2128
    self.dev_path = None
2129
    return True
2130

    
2131
  def Rename(self, new_uid):
2132
    """Re-connect this device to another peer.
2133

2134
    """
2135
    if self.minor is None:
2136
      raise errors.BlockDeviceError("Device not attached during rename")
2137
    if self._rhost is not None:
2138
      # this means we did have a host when we attached, so we are connected
2139
      if not self._ShutdownNet(self.minor):
2140
        raise errors.BlockDeviceError("Can't disconnect from remote peer")
2141
      old_id = self.unique_id
2142
    else:
2143
      old_id = None
2144
    self.unique_id = new_uid
2145
    if not self._AssembleNet(self.minor, self.unique_id, "C"):
2146
      logger.Error("Can't attach to new peer!")
2147
      if self.old_id is not None:
2148
        self._AssembleNet(self.minor, old_id, "C")
2149
      self.unique_id = old_id
2150
      raise errors.BlockDeviceError("Can't attach to new peer")
2151

    
2152
  def Remove(self):
2153
    """Stub remove for DRBD devices.
2154

2155
    """
2156
    return self.Shutdown()
2157

    
2158
  @classmethod
2159
  def Create(cls, unique_id, children, size):
2160
    """Create a new DRBD8 device.
2161

2162
    Since DRBD devices are not created per se, just assembled, this
2163
    function only initializes the metadata.
2164

2165
    """
2166
    if len(children) != 2:
2167
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2168
    meta = children[1]
2169
    meta.Assemble()
2170
    if not meta.Attach():
2171
      raise errors.BlockDeviceError("Can't attach to meta device")
2172
    if not cls._CheckMetaSize(meta.dev_path):
2173
      raise errors.BlockDeviceError("Invalid meta device size")
2174
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2175
    if not cls._IsValidMeta(meta.dev_path):
2176
      raise errors.BlockDeviceError("Cannot initalize meta device")
2177
    return cls(unique_id, children)
2178

    
2179

    
2180
DEV_MAP = {
2181
  constants.LD_LV: LogicalVolume,
2182
  constants.LD_MD_R1: MDRaid1,
2183
  constants.LD_DRBD7: DRBDev,
2184
  constants.LD_DRBD8: DRBD8,
2185
  }
2186

    
2187

    
2188
def FindDevice(dev_type, unique_id, children):
2189
  """Search for an existing, assembled device.
2190

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

2194
  """
2195
  if dev_type not in DEV_MAP:
2196
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2197
  device = DEV_MAP[dev_type](unique_id, children)
2198
  if not device.Attach():
2199
    return None
2200
  return  device
2201

    
2202

    
2203
def AttachOrAssemble(dev_type, unique_id, children):
2204
  """Try to attach or assemble an existing device.
2205

2206
  This will attach to an existing assembled device or will assemble
2207
  the device, as needed, to bring it fully up.
2208

2209
  """
2210
  if dev_type not in DEV_MAP:
2211
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2212
  device = DEV_MAP[dev_type](unique_id, children)
2213
  if not device.Attach():
2214
    device.Assemble()
2215
  if not device.Attach():
2216
    raise errors.BlockDeviceError("Can't find a valid block device for"
2217
                                  " %s/%s/%s" %
2218
                                  (dev_type, unique_id, children))
2219
  return device
2220

    
2221

    
2222
def Create(dev_type, unique_id, children, size):
2223
  """Create a device.
2224

2225
  """
2226
  if dev_type not in DEV_MAP:
2227
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2228
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2229
  return device