Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ fc1dc9d7

History | View | Annotate | Download (69.5 kB)

1
#
2
#
3

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

    
21

    
22
"""Block device abstraction"""
23

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

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

    
34

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

135
    """
136
    raise NotImplementedError
137

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

141
    """
142
    raise NotImplementedError
143

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

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

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

155
    """
156
    raise NotImplementedError
157

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

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

165
    """
166
    raise NotImplementedError
167

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

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

173
    """
174
    raise NotImplementedError
175

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

179
    """
180
    raise NotImplementedError
181

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

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

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

191
    """
192
    raise NotImplementedError
193

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

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

201
    """
202
    raise NotImplementedError
203

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

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

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

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

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

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

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

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

    
235

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

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

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

    
259

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

263
    Only supported for some device types.
264

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

    
269

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

    
275

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

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

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

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

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

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

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

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

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

326
    Args:
327
      vg_name: the volume group name
328

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

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

    
351
    return data
352

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

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

    
365
    return not result.failed
366

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

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

    
385

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

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

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

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

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

414
    """
415
    return True
416

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

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

423
    """
424
    return True
425

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

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

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

    
452
    return retval
453

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

457
    This is a no-op for the LV device type.
458

459
    """
460
    return True
461

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

465
    This is a no-op for the LV device type.
466

467
    """
468
    return True
469

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

473
    """
474
    snap_name = self._lv_name + ".snap"
475

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

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

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

    
497
    return snap_name
498

    
499
  def SetInfo(self, text):
500
    """Update metadata with info text.
501

502
    """
503
    BlockDev.SetInfo(self, text)
504

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

    
509
    # Only up to 128 characters are allowed
510
    text = text[:128]
511

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

    
518

    
519
class MDRaid1(BlockDev):
520
  """raid1 device implemented via md.
521

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

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

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

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

    
541
    return (minor is not None)
542

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

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

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

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

    
563
    return used_md
564

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

569
    Currently only uuid is returned.
570

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

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

591
    This code assumes that there are 256 minors only.
592

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

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

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

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

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

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

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

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

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

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

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

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

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

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

697
    We don't remove the superblock right now. Mark a to do.
698

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

    
703
  def Rename(self, new_id):
704
    """Rename a device.
705

706
    This is not supported for md raid1 devices.
707

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

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

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

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

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

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

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

    
776
  def GetStatus(self):
777
    """Return the status of the device.
778

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

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

790
    This sets our minor variable and our dev_path.
791

792
    """
793
    self.minor = minor
794
    self.dev_path = "/dev/md%d" % minor
795

    
796
  def Assemble(self):
797
    """Assemble the MD device.
798

799
    At this point we should have:
800
      - list of children devices
801
      - uuid
802

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

    
826
  def Shutdown(self):
827
    """Tear down the MD array.
828

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

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

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

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

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

    
865
  def GetSyncStatus(self):
866
    """Returns the sync status of the device.
867

868
    Returns:
869
     (sync_percent, estimated_time, is_degraded)
870

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

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

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

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

909
    """
910
    return True
911

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

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

918
    """
919
    return True
920

    
921

    
922
class BaseDRBD(BlockDev):
923
  """Base DRBD class.
924

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

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

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

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

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

954
    Returns:
955
      a dictionary of minor: joined lines from /proc/drbd for that minor
956

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

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

980
    This will return a list [k_major, k_minor, k_point, api, proto].
981

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

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

995
    """
996
    return "/dev/drbd%d" % minor
997

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

1002
    """
1003
    data = cls._GetProcData()
1004

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

    
1017
    return used_devs
1018

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

1022
    This sets our minor variable and our dev_path.
1023

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

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

1035
    This currently only check the size, which must be around
1036
    128MiB.
1037

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

    
1057
  def Rename(self, new_id):
1058
    """Rename a device.
1059

1060
    This is not supported for drbd devices.
1061

1062
    """
1063
    raise errors.ProgrammerError("Can't rename a drbd device")
1064

    
1065

    
1066
class DRBDev(BaseDRBD):
1067
  """DRBD block device.
1068

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

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

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

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

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

1099
    """
1100
    data = cls._GetProcData()
1101

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

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

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

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

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

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

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

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

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

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

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

    
1201
    if self._lhost is None:
1202
      return False
1203

    
1204
    if not ("local_addr" in info and
1205
            "remote_addr" in info):
1206
      return False
1207

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

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

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

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

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

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

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

    
1242
  @staticmethod
1243
  def _ShutdownAll(minor):
1244
    """Deactivate the device.
1245

1246
    This will, of course, fail if the device is in use.
1247

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

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

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

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

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

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

1297
    This fails if we don't have a local device.
1298

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

    
1304
  def Assemble(self):
1305
    """Assemble the drbd.
1306

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

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

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

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

    
1354
  def Shutdown(self):
1355
    """Shutdown the DRBD device.
1356

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

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

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

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

    
1391
    self._SetFromMinor(minor)
1392
    return minor is not None
1393

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

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

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

    
1415
  def Close(self):
1416
    """Make the local state secondary.
1417

1418
    This will, of course, fail if the device is in use.
1419

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

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

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

    
1443
  def GetSyncStatus(self):
1444
    """Returns the sync status of the device.
1445

1446
    Returns:
1447
     (sync_percent, estimated_time, is_degraded)
1448

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

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

    
1480
  def GetStatus(self):
1481
    """Compute the status of the DRBD device
1482

1483
    Note that DRBD devices don't have the STATUS_EXISTING state.
1484

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

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

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

    
1506
    return result
1507

    
1508
  @staticmethod
1509
  def _ZeroDevice(device):
1510
    """Zero a device.
1511

1512
    This writes until we get ENOSPC.
1513

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

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

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

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

    
1545
  def Remove(self):
1546
    """Stub remove for DRBD devices.
1547

1548
    """
1549
    return self.Shutdown()
1550

    
1551

    
1552
class DRBD8(BaseDRBD):
1553
  """DRBD v8.x block device.
1554

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

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

1564
  """
1565
  _MAX_MINORS = 255
1566
  _PARSE_SHOW = None
1567

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

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

    
1586
  @classmethod
1587
  def _InitMeta(cls, minor, dev_path):
1588
    """Initialize a meta device.
1589

1590
    This will not work if the given minor is in use.
1591

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

    
1599
  @classmethod
1600
  def _FindUnusedMinor(cls):
1601
    """Find an unused DRBD device.
1602

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

1606
    """
1607
    data = cls._GetProcData()
1608

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

    
1627
  @classmethod
1628
  def _IsValidMeta(cls, meta_device):
1629
    """Check if the given meta device looks like a valid one.
1630

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

    
1642
  @classmethod
1643
  def _GetShowParser(cls):
1644
    """Return a parser for `drbd show` output.
1645

1646
    This will either create or return an already-create parser for the
1647
    output of the command `drbd show`.
1648

1649
    """
1650
    if cls._PARSE_SHOW is not None:
1651
      return cls._PARSE_SHOW
1652

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

    
1660
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1661
    defa = pyp.Literal("_is_default").suppress()
1662
    dbl_quote = pyp.Literal('"').suppress()
1663

    
1664
    keyword = pyp.Word(pyp.alphanums + '-')
1665

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

    
1675
    # a statement
1676
    stmt = (~rbrace + keyword + ~lbrace +
1677
            (addr_port ^ value ^ quoted ^ meta_value) +
1678
            pyp.Optional(defa) + semi +
1679
            pyp.Optional(pyp.restOfLine).suppress())
1680

    
1681
    # an entire section
1682
    section_name = pyp.Word(pyp.alphas + '_')
1683
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1684

    
1685
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1686
    bnf.ignore(comment)
1687

    
1688
    cls._PARSE_SHOW = bnf
1689

    
1690
    return bnf
1691

    
1692
  @classmethod
1693
  def _GetDevInfo(cls, minor):
1694
    """Get details about a given DRBD minor.
1695

1696
    This return, if available, the local backing device (as a path)
1697
    and the local and remote (ip, port) information.
1698

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

    
1709
    bnf = cls._GetShowParser()
1710
    # run pyparse
1711

    
1712
    try:
1713
      results = bnf.parseString(out)
1714
    except pyp.ParseException, err:
1715
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1716
                                    str(err))
1717

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

    
1736
  def _MatchesLocal(self, info):
1737
    """Test if our local config matches with an existing device.
1738

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

1744
    """
1745
    if self._children:
1746
      backend, meta = self._children
1747
    else:
1748
      backend = meta = None
1749

    
1750
    if backend is not None:
1751
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1752
    else:
1753
      retval = ("local_dev" not in info)
1754

    
1755
    if meta is not None:
1756
      retval = retval and ("meta_dev" in info and
1757
                           info["meta_dev"] == meta.dev_path)
1758
      retval = retval and ("meta_index" in info and
1759
                           info["meta_index"] == 0)
1760
    else:
1761
      retval = retval and ("meta_dev" not in info and
1762
                           "meta_index" not in info)
1763
    return retval
1764

    
1765
  def _MatchesNet(self, info):
1766
    """Test if our network config matches with an existing device.
1767

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

1773
    """
1774
    if (((self._lhost is None and not ("local_addr" in info)) and
1775
         (self._rhost is None and not ("remote_addr" in info)))):
1776
      return True
1777

    
1778
    if self._lhost is None:
1779
      return False
1780

    
1781
    if not ("local_addr" in info and
1782
            "remote_addr" in info):
1783
      return False
1784

    
1785
    retval = (info["local_addr"] == (self._lhost, self._lport))
1786
    retval = (retval and
1787
              info["remote_addr"] == (self._rhost, self._rport))
1788
    return retval
1789

    
1790
  @classmethod
1791
  def _AssembleLocal(cls, minor, backend, meta):
1792
    """Configure the local part of a DRBD device.
1793

1794
    This is the first thing that must be done on an unconfigured DRBD
1795
    device. And it must be done only once.
1796

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

    
1807
  @classmethod
1808
  def _AssembleNet(cls, minor, net_info, protocol,
1809
                   dual_pri=False, hmac=None, secret=None):
1810
    """Configure the network part of the device.
1811

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

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

    
1847
  def AddChildren(self, devices):
1848
    """Add a disk to the DRBD device.
1849

1850
    """
1851
    if self.minor is None:
1852
      raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1853

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

    
1869
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1870
      raise errors.BlockDeviceError("Can't attach to local storage")
1871
    self._children = devices
1872

    
1873
  def RemoveChildren(self, devices):
1874
    """Detach the drbd device from local storage.
1875

1876
    """
1877
    if self.minor is None:
1878
      raise errors.BlockDeviceError("Can't attach to drbd8 during"
1879
                                    " RemoveChildren")
1880
    if len(self._children) != 2:
1881
      raise errors.BlockDeviceError("We don't have two children: %s" %
1882
                                    self._children)
1883
    if self._children.count(None) == 2: # we don't actually have children :)
1884
      logger.Error("Requested detach while detached")
1885
      return
1886
    if len(devices) != 2:
1887
      raise errors.BlockDeviceError("We need two children in RemoveChildren")
1888
    for child, dev in zip(self._children, devices):
1889
      if dev != child.dev_path:
1890
        raise errors.BlockDeviceError("Mismatch in local storage"
1891
                                      " (%s != %s) in RemoveChildren" %
1892
                                      (dev, child.dev_path))
1893

    
1894
    if not self._ShutdownLocal(self.minor):
1895
      raise errors.BlockDeviceError("Can't detach from local storage")
1896
    self._children = []
1897

    
1898
  def SetSyncSpeed(self, kbytes):
1899
    """Set the speed of the DRBD syncer.
1900

1901
    """
1902
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1903
    if self.minor is None:
1904
      logger.Info("Instance not attached to a device")
1905
      return False
1906
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1907
                           kbytes])
1908
    if result.failed:
1909
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1910
    return not result.failed and children_result
1911

    
1912
  def GetSyncStatus(self):
1913
    """Returns the sync status of the device.
1914

1915
    Returns:
1916
     (sync_percent, estimated_time, is_degraded)
1917

1918
    If sync_percent is None, it means all is ok
1919
    If estimated_time is None, it means we can't esimate
1920
    the time needed, otherwise it's the time left in seconds
1921

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

    
1951
  def GetStatus(self):
1952
    """Compute the status of the DRBD device
1953

1954
    Note that DRBD devices don't have the STATUS_EXISTING state.
1955

1956
    """
1957
    if self.minor is None and not self.Attach():
1958
      return self.STATUS_UNKNOWN
1959

    
1960
    data = self._GetProcData()
1961
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1962
                       self.minor)
1963
    for line in data:
1964
      mresult = match.match(line)
1965
      if mresult:
1966
        break
1967
    else:
1968
      logger.Error("Can't find myself!")
1969
      return self.STATUS_UNKNOWN
1970

    
1971
    state = mresult.group(2)
1972
    if state == "Primary":
1973
      result = self.STATUS_ONLINE
1974
    else:
1975
      result = self.STATUS_STANDBY
1976

    
1977
    return result
1978

    
1979
  def Open(self, force=False):
1980
    """Make the local state primary.
1981

1982
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1983
    is given. Since this is a pottentialy dangerous operation, the
1984
    force flag should be only given after creation, when it actually
1985
    has to be given.
1986

1987
    """
1988
    if self.minor is None and not self.Attach():
1989
      logger.Error("DRBD cannot attach to a device during open")
1990
      return False
1991
    cmd = ["drbdsetup", self.dev_path, "primary"]
1992
    if force:
1993
      cmd.append("-o")
1994
    result = utils.RunCmd(cmd)
1995
    if result.failed:
1996
      logger.Error("Can't make drbd device primary: %s" % result.output)
1997
      return False
1998
    return True
1999

    
2000
  def Close(self):
2001
    """Make the local state secondary.
2002

2003
    This will, of course, fail if the device is in use.
2004

2005
    """
2006
    if self.minor is None and not self.Attach():
2007
      logger.Info("Instance not attached to a device")
2008
      raise errors.BlockDeviceError("Can't find device")
2009
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2010
    if result.failed:
2011
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
2012
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
2013

    
2014
  def Attach(self):
2015
    """Find a DRBD device which matches our config and attach to it.
2016

2017
    In case of partially attached (local device matches but no network
2018
    setup), we perform the network attach. If successful, we re-test
2019
    the attach if can return success.
2020

2021
    """
2022
    for minor in self._GetUsedDevs():
2023
      info = self._GetDevInfo(minor)
2024
      match_l = self._MatchesLocal(info)
2025
      match_r = self._MatchesNet(info)
2026
      if match_l and match_r:
2027
        break
2028
      if match_l and not match_r and "local_addr" not in info:
2029
        res_r = self._AssembleNet(minor,
2030
                                  (self._lhost, self._lport,
2031
                                   self._rhost, self._rport),
2032
                                  "C")
2033
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
2034
          break
2035
      # the weakest case: we find something that is only net attached
2036
      # even though we were passed some children at init time
2037
      if match_r and "local_dev" not in info:
2038
        break
2039
    else:
2040
      minor = None
2041

    
2042
    self._SetFromMinor(minor)
2043
    return minor is not None
2044

    
2045
  def Assemble(self):
2046
    """Assemble the drbd.
2047

2048
    Method:
2049
      - if we have a local backing device, we bind to it by:
2050
        - checking the list of used drbd devices
2051
        - check if the local minor use of any of them is our own device
2052
        - if yes, abort?
2053
        - if not, bind
2054
      - if we have a local/remote net info:
2055
        - redo the local backing device step for the remote device
2056
        - check if any drbd device is using the local port,
2057
          if yes abort
2058
        - check if any remote drbd device is using the remote
2059
          port, if yes abort (for now)
2060
        - bind our net port
2061
        - bind the remote net port
2062

2063
    """
2064
    self.Attach()
2065
    if self.minor is not None:
2066
      logger.Info("Already assembled")
2067
      return True
2068

    
2069
    result = super(DRBD8, self).Assemble()
2070
    if not result:
2071
      return result
2072

    
2073
    minor = self._FindUnusedMinor()
2074
    need_localdev_teardown = False
2075
    if self._children and self._children[0] and self._children[1]:
2076
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2077
                                   self._children[1].dev_path)
2078
      if not result:
2079
        return False
2080
      need_localdev_teardown = True
2081
    if self._lhost and self._lport and self._rhost and self._rport:
2082
      result = self._AssembleNet(minor,
2083
                                 (self._lhost, self._lport,
2084
                                  self._rhost, self._rport),
2085
                                 "C")
2086
      if not result:
2087
        if need_localdev_teardown:
2088
          # we will ignore failures from this
2089
          logger.Error("net setup failed, tearing down local device")
2090
          self._ShutdownAll(minor)
2091
        return False
2092
    self._SetFromMinor(minor)
2093
    return True
2094

    
2095
  @classmethod
2096
  def _ShutdownLocal(cls, minor):
2097
    """Detach from the local device.
2098

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

2102
    """
2103
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2104
    if result.failed:
2105
      logger.Error("Can't detach local device: %s" % result.output)
2106
    return not result.failed
2107

    
2108
  @classmethod
2109
  def _ShutdownNet(cls, minor):
2110
    """Disconnect from the remote peer.
2111

2112
    This fails if we don't have a local device.
2113

2114
    """
2115
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2116
    logger.Error("Can't shutdown network: %s" % result.output)
2117
    return not result.failed
2118

    
2119
  @classmethod
2120
  def _ShutdownAll(cls, minor):
2121
    """Deactivate the device.
2122

2123
    This will, of course, fail if the device is in use.
2124

2125
    """
2126
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2127
    if result.failed:
2128
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2129
    return not result.failed
2130

    
2131
  def Shutdown(self):
2132
    """Shutdown the DRBD device.
2133

2134
    """
2135
    if self.minor is None and not self.Attach():
2136
      logger.Info("DRBD device not attached to a device during Shutdown")
2137
      return True
2138
    if not self._ShutdownAll(self.minor):
2139
      return False
2140
    self.minor = None
2141
    self.dev_path = None
2142
    return True
2143

    
2144
  def Rename(self, new_uid):
2145
    """Re-connect this device to another peer.
2146

2147
    """
2148
    if self.minor is None:
2149
      raise errors.BlockDeviceError("Device not attached during rename")
2150
    if self._rhost is not None:
2151
      # this means we did have a host when we attached, so we are connected
2152
      if not self._ShutdownNet(self.minor):
2153
        raise errors.BlockDeviceError("Can't disconnect from remote peer")
2154
      old_id = self.unique_id
2155
    else:
2156
      old_id = None
2157
    self.unique_id = new_uid
2158
    if not self._AssembleNet(self.minor, self.unique_id, "C"):
2159
      logger.Error("Can't attach to new peer!")
2160
      if old_id is not None:
2161
        self._AssembleNet(self.minor, old_id, "C")
2162
      self.unique_id = old_id
2163
      raise errors.BlockDeviceError("Can't attach to new peer")
2164

    
2165
  def Remove(self):
2166
    """Stub remove for DRBD devices.
2167

2168
    """
2169
    return self.Shutdown()
2170

    
2171
  @classmethod
2172
  def Create(cls, unique_id, children, size):
2173
    """Create a new DRBD8 device.
2174

2175
    Since DRBD devices are not created per se, just assembled, this
2176
    function only initializes the metadata.
2177

2178
    """
2179
    if len(children) != 2:
2180
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2181
    meta = children[1]
2182
    meta.Assemble()
2183
    if not meta.Attach():
2184
      raise errors.BlockDeviceError("Can't attach to meta device")
2185
    if not cls._CheckMetaSize(meta.dev_path):
2186
      raise errors.BlockDeviceError("Invalid meta device size")
2187
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2188
    if not cls._IsValidMeta(meta.dev_path):
2189
      raise errors.BlockDeviceError("Cannot initalize meta device")
2190
    return cls(unique_id, children)
2191

    
2192

    
2193
DEV_MAP = {
2194
  constants.LD_LV: LogicalVolume,
2195
  constants.LD_MD_R1: MDRaid1,
2196
  constants.LD_DRBD7: DRBDev,
2197
  constants.LD_DRBD8: DRBD8,
2198
  }
2199

    
2200

    
2201
def FindDevice(dev_type, unique_id, children):
2202
  """Search for an existing, assembled device.
2203

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

2207
  """
2208
  if dev_type not in DEV_MAP:
2209
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2210
  device = DEV_MAP[dev_type](unique_id, children)
2211
  if not device.Attach():
2212
    return None
2213
  return  device
2214

    
2215

    
2216
def AttachOrAssemble(dev_type, unique_id, children):
2217
  """Try to attach or assemble an existing device.
2218

2219
  This will attach to an existing assembled device or will assemble
2220
  the device, as needed, to bring it fully up.
2221

2222
  """
2223
  if dev_type not in DEV_MAP:
2224
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2225
  device = DEV_MAP[dev_type](unique_id, children)
2226
  if not device.Attach():
2227
    device.Assemble()
2228
  if not device.Attach():
2229
    raise errors.BlockDeviceError("Can't find a valid block device for"
2230
                                  " %s/%s/%s" %
2231
                                  (dev_type, unique_id, children))
2232
  return device
2233

    
2234

    
2235
def Create(dev_type, unique_id, children, size):
2236
  """Create a device.
2237

2238
  """
2239
  if dev_type not in DEV_MAP:
2240
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2241
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2242
  return device