Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ f3e513ad

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

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

    
105

    
106
  def Assemble(self):
107
    """Assemble the device from its components.
108

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

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

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

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

    
134

    
135
  def Attach(self):
136
    """Find a device which matches our config and attach to it.
137

138
    """
139
    raise NotImplementedError
140

    
141

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

145
    """
146
    raise NotImplementedError
147

    
148

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

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

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

160
    """
161
    raise NotImplementedError
162

    
163

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

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

171
    """
172
    raise NotImplementedError
173

    
174

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

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

180
    """
181
    raise NotImplementedError
182

    
183

    
184
  def GetStatus(self):
185
    """Return the status of the device.
186

187
    """
188
    raise NotImplementedError
189

    
190

    
191
  def Open(self, force=False):
192
    """Make the device ready for use.
193

194
    This makes the device ready for I/O. For now, just the DRBD
195
    devices need this.
196

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

200
    """
201
    raise NotImplementedError
202

    
203

    
204
  def Shutdown(self):
205
    """Shut down the device, freeing its children.
206

207
    This undoes the `Assemble()` work, except for the child
208
    assembling; as such, the children on the device are still
209
    assembled after this call.
210

211
    """
212
    raise NotImplementedError
213

    
214

    
215
  def SetSyncSpeed(self, speed):
216
    """Adjust the sync speed of the mirror.
217

218
    In case this is not a mirroring device, this is no-op.
219

220
    """
221
    result = True
222
    if self._children:
223
      for child in self._children:
224
        result = result and child.SetSyncSpeed(speed)
225
    return result
226

    
227

    
228
  def GetSyncStatus(self):
229
    """Returns the sync status of the device.
230

231
    If this device is a mirroring device, this function returns the
232
    status of the mirror.
233

234
    Returns:
235
     (sync_percent, estimated_time, is_degraded)
236

237
    If sync_percent is None, it means all is ok
238
    If estimated_time is None, it means we can't estimate
239
    the time needed, otherwise it's the time left in seconds
240
    If is_degraded is True, it means the device is missing
241
    redundancy. This is usually a sign that something went wrong in
242
    the device setup, if sync_percent is None.
243

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

    
247

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

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

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

    
271

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

275
    Only supported for some device types.
276

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

    
281

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

    
287

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

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

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

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

    
305

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

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

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

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

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

339
    Args:
340
      vg_name: the volume group name
341

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

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

    
364
    return data
365

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

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

    
378
    return not result.failed
379

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

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

    
396
  def Attach(self):
397
    """Attach to an existing LV.
398

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

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

    
418

    
419
  def Assemble(self):
420
    """Assemble the device.
421

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

425
    """
426
    return True
427

    
428

    
429
  def Shutdown(self):
430
    """Shutdown the device.
431

432
    This is a no-op for the LV device type, as we don't deactivate the
433
    volumes on shutdown.
434

435
    """
436
    return True
437

    
438

    
439
  def GetStatus(self):
440
    """Return the status of the device.
441

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

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

    
465
    return retval
466

    
467

    
468
  def Open(self, force=False):
469
    """Make the device ready for I/O.
470

471
    This is a no-op for the LV device type.
472

473
    """
474
    return True
475

    
476

    
477
  def Close(self):
478
    """Notifies that the device will no longer be used for I/O.
479

480
    This is a no-op for the LV device type.
481

482
    """
483
    return True
484

    
485

    
486
  def Snapshot(self, size):
487
    """Create a snapshot copy of an lvm block device.
488

489
    """
490
    snap_name = self._lv_name + ".snap"
491

    
492
    # remove existing snapshot if found
493
    snap = LogicalVolume((self._vg_name, snap_name), None)
494
    snap.Remove()
495

    
496
    pvs_info = self.GetPVInfo(self._vg_name)
497
    if not pvs_info:
498
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
499
                                    self._vg_name)
500
    pvs_info.sort()
501
    pvs_info.reverse()
502
    free_size, pv_name = pvs_info[0]
503
    if free_size < size:
504
      raise errors.BlockDeviceError("Not enough free space: required %s,"
505
                                    " available %s" % (size, free_size))
506

    
507
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
508
                           "-n%s" % snap_name, self.dev_path])
509
    if result.failed:
510
      raise errors.BlockDeviceError("command: %s error: %s" %
511
                                    (result.cmd, result.fail_reason))
512

    
513
    return snap_name
514

    
515

    
516
  def SetInfo(self, text):
517
    """Update metadata with info text.
518

519
    """
520
    BlockDev.SetInfo(self, text)
521

    
522
    # Replace invalid characters
523
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
524
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
525

    
526
    # Only up to 128 characters are allowed
527
    text = text[:128]
528

    
529
    result = utils.RunCmd(["lvchange", "--addtag", text,
530
                           self.dev_path])
531
    if result.failed:
532
      raise errors.BlockDeviceError("Command: %s error: %s" %
533
                                    (result.cmd, result.fail_reason))
534

    
535

    
536
class MDRaid1(BlockDev):
537
  """raid1 device implemented via md.
538

539
  """
540
  def __init__(self, unique_id, children):
541
    super(MDRaid1, self).__init__(unique_id, children)
542
    self.major = 9
543
    self.Attach()
544

    
545

    
546
  def Attach(self):
547
    """Find an array which matches our config and attach to it.
548

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

551
    """
552
    minor = self._FindMDByUUID(self.unique_id)
553
    if minor is not None:
554
      self._SetFromMinor(minor)
555
    else:
556
      self.minor = None
557
      self.dev_path = None
558

    
559
    return (minor is not None)
560

    
561

    
562
  @staticmethod
563
  def _GetUsedDevs():
564
    """Compute the list of in-use MD devices.
565

566
    It doesn't matter if the used device have other raid level, just
567
    that they are in use.
568

569
    """
570
    mdstat = open("/proc/mdstat", "r")
571
    data = mdstat.readlines()
572
    mdstat.close()
573

    
574
    used_md = {}
575
    valid_line = re.compile("^md([0-9]+) : .*$")
576
    for line in data:
577
      match = valid_line.match(line)
578
      if match:
579
        md_no = int(match.group(1))
580
        used_md[md_no] = line
581

    
582
    return used_md
583

    
584

    
585
  @staticmethod
586
  def _GetDevInfo(minor):
587
    """Get info about a MD device.
588

589
    Currently only uuid is returned.
590

591
    """
592
    result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
593
    if result.failed:
594
      logger.Error("Can't display md: %s" % result.fail_reason)
595
      return None
596
    retval = {}
597
    for line in result.stdout.splitlines():
598
      line = line.strip()
599
      kv = line.split(" : ", 1)
600
      if kv:
601
        if kv[0] == "UUID":
602
          retval["uuid"] = kv[1].split()[0]
603
        elif kv[0] == "State":
604
          retval["state"] = kv[1].split(", ")
605
    return retval
606

    
607

    
608
  @staticmethod
609
  def _FindUnusedMinor():
610
    """Compute an unused MD minor.
611

612
    This code assumes that there are 256 minors only.
613

614
    """
615
    used_md = MDRaid1._GetUsedDevs()
616
    i = 0
617
    while i < 256:
618
      if i not in used_md:
619
        break
620
      i += 1
621
    if i == 256:
622
      logger.Error("Critical: Out of md minor numbers.")
623
      raise errors.BlockDeviceError("Can't find a free MD minor")
624
    return i
625

    
626

    
627
  @classmethod
628
  def _FindMDByUUID(cls, uuid):
629
    """Find the minor of an MD array with a given UUID.
630

631
    """
632
    md_list = cls._GetUsedDevs()
633
    for minor in md_list:
634
      info = cls._GetDevInfo(minor)
635
      if info and info["uuid"] == uuid:
636
        return minor
637
    return None
638

    
639

    
640
  @staticmethod
641
  def _ZeroSuperblock(dev_path):
642
    """Zero the possible locations for an MD superblock.
643

644
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
645
    fails in versions 2.x with the same error code as non-writable
646
    device.
647

648
    The superblocks are located at (negative values are relative to
649
    the end of the block device):
650
      - -128k to end for version 0.90 superblock
651
      - -8k to -12k for version 1.0 superblock (included in the above)
652
      - 0k to 4k for version 1.1 superblock
653
      - 4k to 8k for version 1.2 superblock
654

655
    To cover all situations, the zero-ing will be:
656
      - 0k to 128k
657
      - -128k to end
658

659
    As such, the minimum device size must be 128k, otherwise we'll get
660
    I/O errors.
661

662
    Note that this function depends on the fact that one can open,
663
    read and write block devices normally.
664

665
    """
666
    overwrite_size = 128 * 1024
667
    empty_buf = '\0' * overwrite_size
668
    fd = open(dev_path, "r+")
669
    try:
670
      fd.seek(0, 0)
671
      p1 = fd.tell()
672
      fd.write(empty_buf)
673
      p2 = fd.tell()
674
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
675
      fd.seek(-overwrite_size, 2)
676
      p1 = fd.tell()
677
      fd.write(empty_buf)
678
      p2 = fd.tell()
679
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
680
    finally:
681
      fd.close()
682

    
683
  @classmethod
684
  def Create(cls, unique_id, children, size):
685
    """Create a new MD raid1 array.
686

687
    """
688
    if not isinstance(children, (tuple, list)):
689
      raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
690
                       str(children))
691
    for i in children:
692
      if not isinstance(i, BlockDev):
693
        raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
694
    for i in children:
695
      try:
696
        cls._ZeroSuperblock(i.dev_path)
697
      except EnvironmentError, err:
698
        logger.Error("Can't zero superblock for %s: %s" %
699
                     (i.dev_path, str(err)))
700
        return None
701
    minor = cls._FindUnusedMinor()
702
    result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
703
                           "--auto=yes", "--force", "-l1",
704
                           "-n%d" % len(children)] +
705
                          [dev.dev_path for dev in children])
706

    
707
    if result.failed:
708
      logger.Error("Can't create md: %s: %s" % (result.fail_reason,
709
                                                result.output))
710
      return None
711
    info = cls._GetDevInfo(minor)
712
    if not info or not "uuid" in info:
713
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
714
      return None
715
    return MDRaid1(info["uuid"], children)
716

    
717

    
718
  def Remove(self):
719
    """Stub remove function for MD RAID 1 arrays.
720

721
    We don't remove the superblock right now. Mark a to do.
722

723
    """
724
    #TODO: maybe zero superblock on child devices?
725
    return self.Shutdown()
726

    
727
  def Rename(self, new_id):
728
    """Rename a device.
729

730
    This is not supported for md raid1 devices.
731

732
    """
733
    raise errors.ProgrammerError("Can't rename a md raid1 device")
734

    
735
  def AddChildren(self, devices):
736
    """Add new member(s) to 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

    
742
    args = ["mdadm", "-a", self.dev_path]
743
    for dev in devices:
744
      if dev.dev_path is None:
745
        raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
746
      dev.Open()
747
      args.append(dev.dev_path)
748
    result = utils.RunCmd(args)
749
    if result.failed:
750
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
751
                                    result.output)
752
    new_len = len(self._children) + len(devices)
753
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
754
    if result.failed:
755
      raise errors.BlockDeviceError("Can't grow md array: %s" %
756
                                    result.output)
757
    self._children.extend(devices)
758

    
759

    
760
  def RemoveChildren(self, devices):
761
    """Remove member(s) from the md raid1.
762

763
    """
764
    if self.minor is None and not self.Attach():
765
      raise errors.BlockDeviceError("Can't attach to device")
766
    new_len = len(self._children) - len(devices)
767
    if new_len < 1:
768
      raise errors.BlockDeviceError("Can't reduce to less than one child")
769
    args = ["mdadm", "-f", self.dev_path]
770
    orig_devs = []
771
    for dev in devices:
772
      args.append(dev.dev_path)
773
      for c in self._children:
774
        if c.dev_path == dev.dev_path:
775
          orig_devs.append(c)
776
          break
777
      else:
778
        raise errors.BlockDeviceError("Can't find device '%s' for removal" %
779
                                      dev)
780
    result = utils.RunCmd(args)
781
    if result.failed:
782
      raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
783
                                    result.output)
784

    
785
    # it seems here we need a short delay for MD to update its
786
    # superblocks
787
    time.sleep(0.5)
788
    args[1] = "-r"
789
    result = utils.RunCmd(args)
790
    if result.failed:
791
      raise errors.BlockDeviceError("Failed to remove device(s) from array:"
792
                                    " %s" % result.output)
793
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
794
                           "-n", new_len])
795
    if result.failed:
796
      raise errors.BlockDeviceError("Can't shrink md array: %s" %
797
                                    result.output)
798
    for dev in orig_devs:
799
      self._children.remove(dev)
800

    
801

    
802
  def GetStatus(self):
803
    """Return the status of the device.
804

805
    """
806
    self.Attach()
807
    if self.minor is None:
808
      retval = self.STATUS_UNKNOWN
809
    else:
810
      retval = self.STATUS_ONLINE
811
    return retval
812

    
813

    
814
  def _SetFromMinor(self, minor):
815
    """Set our parameters based on the given minor.
816

817
    This sets our minor variable and our dev_path.
818

819
    """
820
    self.minor = minor
821
    self.dev_path = "/dev/md%d" % minor
822

    
823

    
824
  def Assemble(self):
825
    """Assemble the MD device.
826

827
    At this point we should have:
828
      - list of children devices
829
      - uuid
830

831
    """
832
    result = super(MDRaid1, self).Assemble()
833
    if not result:
834
      return result
835
    md_list = self._GetUsedDevs()
836
    for minor in md_list:
837
      info = self._GetDevInfo(minor)
838
      if info and info["uuid"] == self.unique_id:
839
        self._SetFromMinor(minor)
840
        logger.Info("MD array %s already started" % str(self))
841
        return True
842
    free_minor = self._FindUnusedMinor()
843
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
844
                           self.unique_id, "/dev/md%d" % free_minor] +
845
                          [bdev.dev_path for bdev in self._children])
846
    if result.failed:
847
      logger.Error("Can't assemble MD array: %s: %s" %
848
                   (result.fail_reason, result.output))
849
      self.minor = None
850
    else:
851
      self.minor = free_minor
852
    return not result.failed
853

    
854

    
855
  def Shutdown(self):
856
    """Tear down the MD array.
857

858
    This does a 'mdadm --stop' so after this command, the array is no
859
    longer available.
860

861
    """
862
    if self.minor is None and not self.Attach():
863
      logger.Info("MD object not attached to a device")
864
      return True
865

    
866
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
867
    if result.failed:
868
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
869
      return False
870
    self.minor = None
871
    self.dev_path = None
872
    return True
873

    
874

    
875
  def SetSyncSpeed(self, kbytes):
876
    """Set the maximum sync speed for the MD array.
877

878
    """
879
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
880
    if self.minor is None:
881
      logger.Error("MD array not attached to a device")
882
      return False
883
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
884
    try:
885
      f.write("%d" % kbytes)
886
    finally:
887
      f.close()
888
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
889
    try:
890
      f.write("%d" % (kbytes/2))
891
    finally:
892
      f.close()
893
    return result
894

    
895

    
896
  def GetSyncStatus(self):
897
    """Returns the sync status of the device.
898

899
    Returns:
900
     (sync_percent, estimated_time)
901

902
    If sync_percent is None, it means all is ok
903
    If estimated_time is None, it means we can't esimate
904
    the time needed, otherwise it's the time left in seconds
905

906
    """
907
    if self.minor is None and not self.Attach():
908
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
909
    dev_info = self._GetDevInfo(self.minor)
910
    is_clean = ("state" in dev_info and
911
                len(dev_info["state"]) == 1 and
912
                dev_info["state"][0] in ("clean", "active"))
913
    sys_path = "/sys/block/md%s/md/" % self.minor
914
    f = file(sys_path + "sync_action")
915
    sync_status = f.readline().strip()
916
    f.close()
917
    if sync_status == "idle":
918
      return None, None, not is_clean
919
    f = file(sys_path + "sync_completed")
920
    sync_completed = f.readline().strip().split(" / ")
921
    f.close()
922
    if len(sync_completed) != 2:
923
      return 0, None, not is_clean
924
    sync_done, sync_total = [float(i) for i in sync_completed]
925
    sync_percent = 100.0*sync_done/sync_total
926
    f = file(sys_path + "sync_speed")
927
    sync_speed_k = int(f.readline().strip())
928
    if sync_speed_k == 0:
929
      time_est = None
930
    else:
931
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
932
    return sync_percent, time_est, not is_clean
933

    
934

    
935
  def Open(self, force=False):
936
    """Make the device ready for I/O.
937

938
    This is a no-op for the MDRaid1 device type, although we could use
939
    the 2.6.18's new array_state thing.
940

941
    """
942
    return True
943

    
944

    
945
  def Close(self):
946
    """Notifies that the device will no longer be used for I/O.
947

948
    This is a no-op for the MDRaid1 device type, but see comment for
949
    `Open()`.
950

951
    """
952
    return True
953

    
954

    
955
class BaseDRBD(BlockDev):
956
  """Base DRBD class.
957

958
  This class contains a few bits of common functionality between the
959
  0.7 and 8.x versions of DRBD.
960

961
  """
962
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
963
                           r" \(api:(\d+)/proto:(\d+)\)")
964
  _DRBD_MAJOR = 147
965
  _ST_UNCONFIGURED = "Unconfigured"
966
  _ST_WFCONNECTION = "WFConnection"
967
  _ST_CONNECTED = "Connected"
968

    
969
  @staticmethod
970
  def _GetProcData():
971
    """Return data from /proc/drbd.
972

973
    """
974
    stat = open("/proc/drbd", "r")
975
    try:
976
      data = stat.read().splitlines()
977
    finally:
978
      stat.close()
979
    if not data:
980
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
981
    return data
982

    
983
  @staticmethod
984
  def _MassageProcData(data):
985
    """Transform the output of _GetProdData into a nicer form.
986

987
    Returns:
988
      a dictionary of minor: joined lines from /proc/drbd for that minor
989

990
    """
991
    lmatch = re.compile("^ *([0-9]+):.*$")
992
    results = {}
993
    old_minor = old_line = None
994
    for line in data:
995
      lresult = lmatch.match(line)
996
      if lresult is not None:
997
        if old_minor is not None:
998
          results[old_minor] = old_line
999
        old_minor = int(lresult.group(1))
1000
        old_line = line
1001
      else:
1002
        if old_minor is not None:
1003
          old_line += " " + line.strip()
1004
    # add last line
1005
    if old_minor is not None:
1006
      results[old_minor] = old_line
1007
    return results
1008

    
1009
  @classmethod
1010
  def _GetVersion(cls):
1011
    """Return the DRBD version.
1012

1013
    This will return a list [k_major, k_minor, k_point, api, proto].
1014

1015
    """
1016
    proc_data = cls._GetProcData()
1017
    first_line = proc_data[0].strip()
1018
    version = cls._VERSION_RE.match(first_line)
1019
    if not version:
1020
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1021
                                    first_line)
1022
    return [int(val) for val in version.groups()]
1023

    
1024
  @staticmethod
1025
  def _DevPath(minor):
1026
    """Return the path to a drbd device for a given minor.
1027

1028
    """
1029
    return "/dev/drbd%d" % minor
1030

    
1031
  @classmethod
1032
  def _GetUsedDevs(cls):
1033
    """Compute the list of used DRBD devices.
1034

1035
    """
1036
    data = cls._GetProcData()
1037

    
1038
    used_devs = {}
1039
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1040
    for line in data:
1041
      match = valid_line.match(line)
1042
      if not match:
1043
        continue
1044
      minor = int(match.group(1))
1045
      state = match.group(2)
1046
      if state == cls._ST_UNCONFIGURED:
1047
        continue
1048
      used_devs[minor] = state, line
1049

    
1050
    return used_devs
1051

    
1052
  def _SetFromMinor(self, minor):
1053
    """Set our parameters based on the given minor.
1054

1055
    This sets our minor variable and our dev_path.
1056

1057
    """
1058
    if minor is None:
1059
      self.minor = self.dev_path = None
1060
    else:
1061
      self.minor = minor
1062
      self.dev_path = self._DevPath(minor)
1063

    
1064
  @staticmethod
1065
  def _CheckMetaSize(meta_device):
1066
    """Check if the given meta device looks like a valid one.
1067

1068
    This currently only check the size, which must be around
1069
    128MiB.
1070

1071
    """
1072
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1073
    if result.failed:
1074
      logger.Error("Failed to get device size: %s" % result.fail_reason)
1075
      return False
1076
    try:
1077
      sectors = int(result.stdout)
1078
    except ValueError:
1079
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1080
      return False
1081
    bytes = sectors * 512
1082
    if bytes < 128 * 1024 * 1024: # less than 128MiB
1083
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1084
      return False
1085
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1086
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1087
      return False
1088
    return True
1089

    
1090
  def Rename(self, new_id):
1091
    """Rename a device.
1092

1093
    This is not supported for drbd devices.
1094

1095
    """
1096
    raise errors.ProgrammerError("Can't rename a drbd device")
1097

    
1098

    
1099
class DRBDev(BaseDRBD):
1100
  """DRBD block device.
1101

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

1106
  The unique_id for the drbd device is the (local_ip, local_port,
1107
  remote_ip, remote_port) tuple, and it must have two children: the
1108
  data device and the meta_device. The meta device is checked for
1109
  valid size and is zeroed on create.
1110

1111
  """
1112
  def __init__(self, unique_id, children):
1113
    super(DRBDev, self).__init__(unique_id, children)
1114
    self.major = self._DRBD_MAJOR
1115
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1116
    if kmaj != 0 and kmin != 7:
1117
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1118
                                    " requested ganeti usage: kernel is"
1119
                                    " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1120

    
1121
    if len(children) != 2:
1122
      raise ValueError("Invalid configuration data %s" % str(children))
1123
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1124
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1125
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1126
    self.Attach()
1127

    
1128
  @classmethod
1129
  def _FindUnusedMinor(cls):
1130
    """Find an unused DRBD device.
1131

1132
    """
1133
    data = cls._GetProcData()
1134

    
1135
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1136
    for line in data:
1137
      match = valid_line.match(line)
1138
      if match:
1139
        return int(match.group(1))
1140
    logger.Error("Error: no free drbd minors!")
1141
    raise errors.BlockDeviceError("Can't find a free DRBD minor")
1142

    
1143
  @classmethod
1144
  def _GetDevInfo(cls, minor):
1145
    """Get details about a given DRBD minor.
1146

1147
    This return, if available, the local backing device in (major,
1148
    minor) formant and the local and remote (ip, port) information.
1149

1150
    """
1151
    data = {}
1152
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1153
    if result.failed:
1154
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1155
      return data
1156
    out = result.stdout
1157
    if out == "Not configured\n":
1158
      return data
1159
    for line in out.splitlines():
1160
      if "local_dev" not in data:
1161
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1162
        if match:
1163
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1164
          continue
1165
      if "meta_dev" not in data:
1166
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1167
        if match:
1168
          if match.group(2) is not None and match.group(3) is not None:
1169
            # matched on the major/minor
1170
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1171
          else:
1172
            # matched on the "internal" string
1173
            data["meta_dev"] = match.group(1)
1174
            # in this case, no meta_index is in the output
1175
            data["meta_index"] = -1
1176
          continue
1177
      if "meta_index" not in data:
1178
        match = re.match("^Meta index: ([0-9]+).*$", line)
1179
        if match:
1180
          data["meta_index"] = int(match.group(1))
1181
          continue
1182
      if "local_addr" not in data:
1183
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1184
        if match:
1185
          data["local_addr"] = (match.group(1), int(match.group(2)))
1186
          continue
1187
      if "remote_addr" not in data:
1188
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1189
        if match:
1190
          data["remote_addr"] = (match.group(1), int(match.group(2)))
1191
          continue
1192
    return data
1193

    
1194

    
1195
  def _MatchesLocal(self, info):
1196
    """Test if our local config matches with an existing device.
1197

1198
    The parameter should be as returned from `_GetDevInfo()`. This
1199
    method tests if our local backing device is the same as the one in
1200
    the info parameter, in effect testing if we look like the given
1201
    device.
1202

1203
    """
1204
    if not ("local_dev" in info and "meta_dev" in info and
1205
            "meta_index" in info):
1206
      return False
1207

    
1208
    backend = self._children[0]
1209
    if backend is not None:
1210
      retval = (info["local_dev"] == (backend.major, backend.minor))
1211
    else:
1212
      retval = (info["local_dev"] == (0, 0))
1213
    meta = self._children[1]
1214
    if meta is not None:
1215
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1216
      retval = retval and (info["meta_index"] == 0)
1217
    else:
1218
      retval = retval and (info["meta_dev"] == "internal" and
1219
                           info["meta_index"] == -1)
1220
    return retval
1221

    
1222

    
1223
  def _MatchesNet(self, info):
1224
    """Test if our network config matches with an existing device.
1225

1226
    The parameter should be as returned from `_GetDevInfo()`. This
1227
    method tests if our network configuration is the same as the one
1228
    in the info parameter, in effect testing if we look like the given
1229
    device.
1230

1231
    """
1232
    if (((self._lhost is None and not ("local_addr" in info)) and
1233
         (self._rhost is None and not ("remote_addr" in info)))):
1234
      return True
1235

    
1236
    if self._lhost is None:
1237
      return False
1238

    
1239
    if not ("local_addr" in info and
1240
            "remote_addr" in info):
1241
      return False
1242

    
1243
    retval = (info["local_addr"] == (self._lhost, self._lport))
1244
    retval = (retval and
1245
              info["remote_addr"] == (self._rhost, self._rport))
1246
    return retval
1247

    
1248

    
1249
  @classmethod
1250
  def _AssembleLocal(cls, minor, backend, meta):
1251
    """Configure the local part of a DRBD device.
1252

1253
    This is the first thing that must be done on an unconfigured DRBD
1254
    device. And it must be done only once.
1255

1256
    """
1257
    if not cls._CheckMetaSize(meta):
1258
      return False
1259
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1260
                           backend, meta, "0", "-e", "detach"])
1261
    if result.failed:
1262
      logger.Error("Can't attach local disk: %s" % result.output)
1263
    return not result.failed
1264

    
1265

    
1266
  @classmethod
1267
  def _ShutdownLocal(cls, minor):
1268
    """Detach from the local device.
1269

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

1273
    """
1274
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1275
    if result.failed:
1276
      logger.Error("Can't detach local device: %s" % result.output)
1277
    return not result.failed
1278

    
1279

    
1280
  @staticmethod
1281
  def _ShutdownAll(minor):
1282
    """Deactivate the device.
1283

1284
    This will, of course, fail if the device is in use.
1285

1286
    """
1287
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1288
    if result.failed:
1289
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1290
    return not result.failed
1291

    
1292

    
1293
  @classmethod
1294
  def _AssembleNet(cls, minor, net_info, protocol):
1295
    """Configure the network part of the device.
1296

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

1304
    """
1305
    lhost, lport, rhost, rport = net_info
1306
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1307
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1308
                           protocol])
1309
    if result.failed:
1310
      logger.Error("Can't setup network for dbrd device: %s" %
1311
                   result.fail_reason)
1312
      return False
1313

    
1314
    timeout = time.time() + 10
1315
    ok = False
1316
    while time.time() < timeout:
1317
      info = cls._GetDevInfo(minor)
1318
      if not "local_addr" in info or not "remote_addr" in info:
1319
        time.sleep(1)
1320
        continue
1321
      if (info["local_addr"] != (lhost, lport) or
1322
          info["remote_addr"] != (rhost, rport)):
1323
        time.sleep(1)
1324
        continue
1325
      ok = True
1326
      break
1327
    if not ok:
1328
      logger.Error("Timeout while configuring network")
1329
      return False
1330
    return True
1331

    
1332

    
1333
  @classmethod
1334
  def _ShutdownNet(cls, minor):
1335
    """Disconnect from the remote peer.
1336

1337
    This fails if we don't have a local device.
1338

1339
    """
1340
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1341
    logger.Error("Can't shutdown network: %s" % result.output)
1342
    return not result.failed
1343

    
1344

    
1345
  def Assemble(self):
1346
    """Assemble the drbd.
1347

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

1363
    """
1364
    self.Attach()
1365
    if self.minor is not None:
1366
      logger.Info("Already assembled")
1367
      return True
1368

    
1369
    result = super(DRBDev, self).Assemble()
1370
    if not result:
1371
      return result
1372

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

    
1395

    
1396
  def Shutdown(self):
1397
    """Shutdown the DRBD device.
1398

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

    
1409

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

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

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

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

    
1437

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

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

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

    
1459

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

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

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

    
1474

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

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

    
1489

    
1490
  def GetSyncStatus(self):
1491
    """Returns the sync status of the device.
1492

1493
    Returns:
1494
     (sync_percent, estimated_time)
1495

1496
    If sync_percent is None, it means all is ok
1497
    If estimated_time is None, it means we can't esimate
1498
    the time needed, otherwise it's the time left in seconds
1499

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

    
1527

    
1528

    
1529

    
1530
  def GetStatus(self):
1531
    """Compute the status of the DRBD device
1532

1533
    Note that DRBD devices don't have the STATUS_EXISTING state.
1534

1535
    """
1536
    if self.minor is None and not self.Attach():
1537
      return self.STATUS_UNKNOWN
1538

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

    
1550
    state = mresult.group(2)
1551
    if state == "Primary":
1552
      result = self.STATUS_ONLINE
1553
    else:
1554
      result = self.STATUS_STANDBY
1555

    
1556
    return result
1557

    
1558

    
1559
  @staticmethod
1560
  def _ZeroDevice(device):
1561
    """Zero a device.
1562

1563
    This writes until we get ENOSPC.
1564

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

    
1575

    
1576
  @classmethod
1577
  def Create(cls, unique_id, children, size):
1578
    """Create a new DRBD device.
1579

1580
    Since DRBD devices are not created per se, just assembled, this
1581
    function just zeroes the meta device.
1582

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

    
1597

    
1598
  def Remove(self):
1599
    """Stub remove for DRBD devices.
1600

1601
    """
1602
    return self.Shutdown()
1603

    
1604

    
1605
class DRBD8(BaseDRBD):
1606
  """DRBD v8.x block device.
1607

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

1612
  The unique_id for the drbd device is the (local_ip, local_port,
1613
  remote_ip, remote_port) tuple, and it must have two children: the
1614
  data device and the meta_device. The meta device is checked for
1615
  valid size and is zeroed on create.
1616

1617
  """
1618
  _DRBD_MAJOR = 147
1619
  _MAX_MINORS = 255
1620
  _PARSE_SHOW = None
1621

    
1622
  def __init__(self, unique_id, children):
1623
    super(DRBD8, self).__init__(unique_id, children)
1624
    self.major = self._DRBD_MAJOR
1625
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1626
    if kmaj != 8:
1627
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1628
                                    " requested ganeti usage: kernel is"
1629
                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1630

    
1631
    if len(children) != 2:
1632
      raise ValueError("Invalid configuration data %s" % str(children))
1633
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1634
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1635
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1636
    self.Attach()
1637

    
1638
  @classmethod
1639
  def _InitMeta(cls, minor, dev_path):
1640
    """Initialize a meta device.
1641

1642
    This will not work if the given minor is in use.
1643

1644
    """
1645
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1646
                           "v08", dev_path, "0", "create-md"])
1647
    if result.failed:
1648
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1649
                                    result.output)
1650

    
1651
  @classmethod
1652
  def _FindUnusedMinor(cls):
1653
    """Find an unused DRBD device.
1654

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

1658
    """
1659
    data = cls._GetProcData()
1660

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

    
1679
  @classmethod
1680
  def _IsValidMeta(cls, meta_device):
1681
    """Check if the given meta device looks like a valid one.
1682

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

    
1694
  @classmethod
1695
  def _GetShowParser(cls):
1696
    """Return a parser for `drbd show` output.
1697

1698
    This will either create or return an already-create parser for the
1699
    output of the command `drbd show`.
1700

1701
    """
1702
    if cls._PARSE_SHOW is not None:
1703
      return cls._PARSE_SHOW
1704

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

    
1712
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1713
    defa = pyp.Literal("_is_default").suppress()
1714
    dbl_quote = pyp.Literal('"').suppress()
1715

    
1716
    keyword = pyp.Word(pyp.alphanums + '-')
1717

    
1718
    # value types
1719
    value = pyp.Word(pyp.alphanums + '_-/.:')
1720
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1721
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1722
                 number)
1723
    # meta device, extended syntax
1724
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1725
                  number + pyp.Word(']').suppress())
1726

    
1727
    # a statement
1728
    stmt = (~rbrace + keyword + ~lbrace +
1729
            (addr_port ^ value ^ quoted ^ meta_value) +
1730
            pyp.Optional(defa) + semi +
1731
            pyp.Optional(pyp.restOfLine).suppress())
1732

    
1733
    # an entire section
1734
    section_name = pyp.Word(pyp.alphas + '_')
1735
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1736

    
1737
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1738
    bnf.ignore(comment)
1739

    
1740
    cls._PARSE_SHOW = bnf
1741

    
1742
    return bnf
1743

    
1744
  @classmethod
1745
  def _GetDevInfo(cls, minor):
1746
    """Get details about a given DRBD minor.
1747

1748
    This return, if available, the local backing device (as a path)
1749
    and the local and remote (ip, port) information.
1750

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

    
1761
    bnf = cls._GetShowParser()
1762
    # run pyparse
1763

    
1764
    try:
1765
      results = bnf.parseString(out)
1766
    except pyp.ParseException, err:
1767
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1768
                                    str(err))
1769

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

    
1788
  def _MatchesLocal(self, info):
1789
    """Test if our local config matches with an existing device.
1790

1791
    The parameter should be as returned from `_GetDevInfo()`. This
1792
    method tests if our local backing device is the same as the one in
1793
    the info parameter, in effect testing if we look like the given
1794
    device.
1795

1796
    """
1797
    backend = self._children[0]
1798
    if backend is not None:
1799
      retval = (info["local_dev"] == backend.dev_path)
1800
    else:
1801
      retval = ("local_dev" not in info)
1802
    meta = self._children[1]
1803
    if meta is not None:
1804
      retval = retval and (info["meta_dev"] == meta.dev_path)
1805
      retval = retval and (info["meta_index"] == 0)
1806
    else:
1807
      retval = retval and ("meta_dev" not in info and
1808
                           "meta_index" not in info)
1809
    return retval
1810

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

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

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

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

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

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

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

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

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

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

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

    
1875
    timeout = time.time() + 10
1876
    ok = False
1877
    while time.time() < timeout:
1878
      info = cls._GetDevInfo(minor)
1879
      if not "local_addr" in info or not "remote_addr" in info:
1880
        time.sleep(1)
1881
        continue
1882
      if (info["local_addr"] != (lhost, lport) or
1883
          info["remote_addr"] != (rhost, rport)):
1884
        time.sleep(1)
1885
        continue
1886
      ok = True
1887
      break
1888
    if not ok:
1889
      logger.Error("Timeout while configuring network")
1890
      return False
1891
    return True
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 _ShutdownNet(cls, minor):
2086
    """Disconnect from the remote peer.
2087

2088
    This fails if we don't have a local device.
2089

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

    
2095
  @classmethod
2096
  def _ShutdownAll(cls, minor):
2097
    """Deactivate the device.
2098

2099
    This will, of course, fail if the device is in use.
2100

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

    
2107
  def Shutdown(self):
2108
    """Shutdown the DRBD device.
2109

2110
    """
2111
    if self.minor is None and not self.Attach():
2112
      logger.Info("DRBD device not attached to a device during Shutdown")
2113
      return True
2114
    if not self._ShutdownAll(self.minor):
2115
      return False
2116
    self.minor = None
2117
    self.dev_path = None
2118
    return True
2119

    
2120
  def Rename(self, new_uid):
2121
    """Re-connect this device to another peer.
2122

2123
    """
2124
    if self.minor is None:
2125
      raise errors.BlockDeviceError("Device not attached during rename")
2126
    if self._rhost is not None:
2127
      # this means we did have a host when we attached, so we are connected
2128
      if not self._ShutdownNet(self.minor):
2129
        raise errors.BlockDeviceError("Can't disconnect from remote peer")
2130
      old_id = self.unique_id
2131
    else:
2132
      old_id = None
2133
    self.unique_id = new_uid
2134
    if not self._AssembleNet(self.minor, self.unique_id, "C"):
2135
      logger.Error("Can't attach to new peer!")
2136
      if self.old_id is not None:
2137
        self._AssembleNet(self.minor, old_id, "C")
2138
      self.unique_id = old_id
2139
      raise errors.BlockDeviceError("Can't attach to new peer")
2140

    
2141
  def Remove(self):
2142
    """Stub remove for DRBD devices.
2143

2144
    """
2145
    return self.Shutdown()
2146

    
2147
  @classmethod
2148
  def Create(cls, unique_id, children, size):
2149
    """Create a new DRBD8 device.
2150

2151
    Since DRBD devices are not created per se, just assembled, this
2152
    function only initializes the metadata.
2153

2154
    """
2155
    if len(children) != 2:
2156
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2157
    meta = children[1]
2158
    meta.Assemble()
2159
    if not meta.Attach():
2160
      raise errors.BlockDeviceError("Can't attach to meta device")
2161
    if not cls._CheckMetaSize(meta.dev_path):
2162
      raise errors.BlockDeviceError("Invalid meta device size")
2163
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2164
    if not cls._IsValidMeta(meta.dev_path):
2165
      raise errors.BlockDeviceError("Cannot initalize meta device")
2166
    return cls(unique_id, children)
2167

    
2168

    
2169
DEV_MAP = {
2170
  constants.LD_LV: LogicalVolume,
2171
  constants.LD_MD_R1: MDRaid1,
2172
  constants.LD_DRBD7: DRBDev,
2173
  constants.LD_DRBD8: DRBD8,
2174
  }
2175

    
2176

    
2177
def FindDevice(dev_type, unique_id, children):
2178
  """Search for an existing, assembled device.
2179

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

2183
  """
2184
  if dev_type not in DEV_MAP:
2185
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2186
  device = DEV_MAP[dev_type](unique_id, children)
2187
  if not device.Attach():
2188
    return None
2189
  return  device
2190

    
2191

    
2192
def AttachOrAssemble(dev_type, unique_id, children):
2193
  """Try to attach or assemble an existing device.
2194

2195
  This will attach to an existing assembled device or will assemble
2196
  the device, as needed, to bring it fully up.
2197

2198
  """
2199
  if dev_type not in DEV_MAP:
2200
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2201
  device = DEV_MAP[dev_type](unique_id, children)
2202
  if not device.Attach():
2203
    device.Assemble()
2204
  if not device.Attach():
2205
    raise errors.BlockDeviceError("Can't find a valid block device for"
2206
                                  " %s/%s/%s" %
2207
                                  (dev_type, unique_id, children))
2208
  return device
2209

    
2210

    
2211
def Create(dev_type, unique_id, children, size):
2212
  """Create a device.
2213

2214
  """
2215
  if dev_type not in DEV_MAP:
2216
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2217
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2218
  return device