Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 24818e8f

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
  @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.dev_path)
748
      for c in self._children:
749
        if c.dev_path == dev.dev_path:
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)
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)
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
    super(DRBD8, self).__init__(unique_id, children)
1570
    self.major = self._DRBD_MAJOR
1571
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1572
    if kmaj != 8:
1573
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1574
                                    " requested ganeti usage: kernel is"
1575
                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1576

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

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

1588
    This will not work if the given minor is in use.
1589

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

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

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

1604
    """
1605
    data = cls._GetProcData()
1606

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

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

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

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

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

1647
    """
1648
    if cls._PARSE_SHOW is not None:
1649
      return cls._PARSE_SHOW
1650

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

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

    
1662
    keyword = pyp.Word(pyp.alphanums + '-')
1663

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

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

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

    
1683
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1684
    bnf.ignore(comment)
1685

    
1686
    cls._PARSE_SHOW = bnf
1687

    
1688
    return bnf
1689

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

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

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

    
1707
    bnf = cls._GetShowParser()
1708
    # run pyparse
1709

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

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

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

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

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

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

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

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

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

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

    
1776
    if self._lhost is None:
1777
      return False
1778

    
1779
    if not ("local_addr" in info and
1780
            "remote_addr" in info):
1781
      return False
1782

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1907
  def GetSyncStatus(self):
1908
    """Returns the sync status of the device.
1909

1910
    Returns:
1911
     (sync_percent, estimated_time)
1912

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

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

    
1944
  def GetStatus(self):
1945
    """Compute the status of the DRBD device
1946

1947
    Note that DRBD devices don't have the STATUS_EXISTING state.
1948

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

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

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

    
1970
    return result
1971

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

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

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

    
1993
  def Close(self):
1994
    """Make the local state secondary.
1995

1996
    This will, of course, fail if the device is in use.
1997

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

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

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

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

    
2031
    self._SetFromMinor(minor)
2032
    return minor is not None
2033

    
2034
  def Assemble(self):
2035
    """Assemble the drbd.
2036

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

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

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

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

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

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

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

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

2101
    This fails if we don't have a local device.
2102

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

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

2112
    This will, of course, fail if the device is in use.
2113

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

    
2120
  def Shutdown(self):
2121
    """Shutdown the DRBD device.
2122

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

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

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

    
2154
  def Remove(self):
2155
    """Stub remove for DRBD devices.
2156

2157
    """
2158
    return self.Shutdown()
2159

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

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

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

    
2181

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

    
2189

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

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

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

    
2204

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

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

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

    
2223

    
2224
def Create(dev_type, unique_id, children, size):
2225
  """Create a device.
2226

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