Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 333411a7

History | View | Annotate | Download (73.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
import os
29

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

    
35

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

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

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

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

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

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

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

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

81
  """
82
  def __init__(self, unique_id, children):
83
    self._children = children
84
    self.dev_path = None
85
    self.unique_id = unique_id
86
    self.major = None
87
    self.minor = None
88

    
89
  def Assemble(self):
90
    """Assemble the device from its components.
91

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

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

100
    """
101
    status = True
102
    for child in self._children:
103
      if not isinstance(child, BlockDev):
104
        raise TypeError("Invalid child passed of type '%s'" % type(child))
105
      if not status:
106
        break
107
      status = status and child.Assemble()
108
      if not status:
109
        break
110

    
111
      try:
112
        child.Open()
113
      except errors.BlockDeviceError:
114
        for child in self._children:
115
          child.Shutdown()
116
        raise
117

    
118
    if not status:
119
      for child in self._children:
120
        child.Shutdown()
121
    return status
122

    
123
  def Attach(self):
124
    """Find a device which matches our config and attach to it.
125

126
    """
127
    raise NotImplementedError
128

    
129
  def Close(self):
130
    """Notifies that the device will no longer be used for I/O.
131

132
    """
133
    raise NotImplementedError
134

    
135
  @classmethod
136
  def Create(cls, unique_id, children, size):
137
    """Create the device.
138

139
    If the device cannot be created, it will return None
140
    instead. Error messages go to the logging system.
141

142
    Note that for some devices, the unique_id is used, and for other,
143
    the children. The idea is that these two, taken together, are
144
    enough for both creation and assembly (later).
145

146
    """
147
    raise NotImplementedError
148

    
149
  def Remove(self):
150
    """Remove this device.
151

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

156
    """
157
    raise NotImplementedError
158

    
159
  def Rename(self, new_id):
160
    """Rename this device.
161

162
    This may or may not make sense for a given device type.
163

164
    """
165
    raise NotImplementedError
166

    
167
  def Open(self, force=False):
168
    """Make the device ready for use.
169

170
    This makes the device ready for I/O. For now, just the DRBD
171
    devices need this.
172

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

176
    """
177
    raise NotImplementedError
178

    
179
  def Shutdown(self):
180
    """Shut down the device, freeing its children.
181

182
    This undoes the `Assemble()` work, except for the child
183
    assembling; as such, the children on the device are still
184
    assembled after this call.
185

186
    """
187
    raise NotImplementedError
188

    
189
  def SetSyncSpeed(self, speed):
190
    """Adjust the sync speed of the mirror.
191

192
    In case this is not a mirroring device, this is no-op.
193

194
    """
195
    result = True
196
    if self._children:
197
      for child in self._children:
198
        result = result and child.SetSyncSpeed(speed)
199
    return result
200

    
201
  def GetSyncStatus(self):
202
    """Returns the sync status of the device.
203

204
    If this device is a mirroring device, this function returns the
205
    status of the mirror.
206

207
    Returns:
208
     (sync_percent, estimated_time, is_degraded, ldisk)
209

210
    If sync_percent is None, it means the device is not syncing.
211

212
    If estimated_time is None, it means we can't estimate
213
    the time needed, otherwise it's the time left in seconds.
214

215
    If is_degraded is True, it means the device is missing
216
    redundancy. This is usually a sign that something went wrong in
217
    the device setup, if sync_percent is None.
218

219
    The ldisk parameter represents the degradation of the local
220
    data. This is only valid for some devices, the rest will always
221
    return False (not degraded).
222

223
    """
224
    return None, None, False, False
225

    
226

    
227
  def CombinedSyncStatus(self):
228
    """Calculate the mirror status recursively for our children.
229

230
    The return value is the same as for `GetSyncStatus()` except the
231
    minimum percent and maximum time are calculated across our
232
    children.
233

234
    """
235
    min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
236
    if self._children:
237
      for child in self._children:
238
        c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
239
        if min_percent is None:
240
          min_percent = c_percent
241
        elif c_percent is not None:
242
          min_percent = min(min_percent, c_percent)
243
        if max_time is None:
244
          max_time = c_time
245
        elif c_time is not None:
246
          max_time = max(max_time, c_time)
247
        is_degraded = is_degraded or c_degraded
248
        ldisk = ldisk or c_ldisk
249
    return min_percent, max_time, is_degraded, ldisk
250

    
251

    
252
  def SetInfo(self, text):
253
    """Update metadata with info text.
254

255
    Only supported for some device types.
256

257
    """
258
    for child in self._children:
259
      child.SetInfo(text)
260

    
261

    
262
  def __repr__(self):
263
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
264
            (self.__class__, self.unique_id, self._children,
265
             self.major, self.minor, self.dev_path))
266

    
267

    
268
class LogicalVolume(BlockDev):
269
  """Logical Volume block device.
270

271
  """
272
  def __init__(self, unique_id, children):
273
    """Attaches to a LV device.
274

275
    The unique_id is a tuple (vg_name, lv_name)
276

277
    """
278
    super(LogicalVolume, self).__init__(unique_id, children)
279
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
280
      raise ValueError("Invalid configuration data %s" % str(unique_id))
281
    self._vg_name, self._lv_name = unique_id
282
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
283
    self.Attach()
284

    
285
  @classmethod
286
  def Create(cls, unique_id, children, size):
287
    """Create a new logical volume.
288

289
    """
290
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
291
      raise ValueError("Invalid configuration data %s" % str(unique_id))
292
    vg_name, lv_name = unique_id
293
    pvs_info = cls.GetPVInfo(vg_name)
294
    if not pvs_info:
295
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
296
                                    vg_name)
297
    pvs_info.sort()
298
    pvs_info.reverse()
299

    
300
    pvlist = [ pv[1] for pv in pvs_info ]
301
    free_size = sum([ pv[0] for pv in pvs_info ])
302

    
303
    # The size constraint should have been checked from the master before
304
    # calling the create function.
305
    if free_size < size:
306
      raise errors.BlockDeviceError("Not enough free space: required %s,"
307
                                    " available %s" % (size, free_size))
308
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
309
                           vg_name] + pvlist)
310
    if result.failed:
311
      raise errors.BlockDeviceError("%s - %s" % (result.fail_reason,
312
                                                result.output))
313
    return LogicalVolume(unique_id, children)
314

    
315
  @staticmethod
316
  def GetPVInfo(vg_name):
317
    """Get the free space info for PVs in a volume group.
318

319
    Args:
320
      vg_name: the volume group name
321

322
    Returns:
323
      list of (free_space, name) with free_space in mebibytes
324

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

    
345
    return data
346

    
347
  def Remove(self):
348
    """Remove this logical volume.
349

350
    """
351
    if not self.minor and not self.Attach():
352
      # the LV does not exist
353
      return True
354
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
355
                           (self._vg_name, self._lv_name)])
356
    if result.failed:
357
      logger.Error("Can't lvremove: %s - %s" %
358
                   (result.fail_reason, result.output))
359

    
360
    return not result.failed
361

    
362
  def Rename(self, new_id):
363
    """Rename this logical volume.
364

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

    
380
  def Attach(self):
381
    """Attach to an existing LV.
382

383
    This method will try to see if an existing and active LV exists
384
    which matches our name. If so, its major/minor will be
385
    recorded.
386

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

    
402
  def Assemble(self):
403
    """Assemble the device.
404

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

409
    """
410
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
411
    if result.failed:
412
      logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
413
    return not result.failed
414

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

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

421
    """
422
    return True
423

    
424
  def GetSyncStatus(self):
425
    """Returns the sync status of the device.
426

427
    If this device is a mirroring device, this function returns the
428
    status of the mirror.
429

430
    Returns:
431
     (sync_percent, estimated_time, is_degraded, ldisk)
432

433
    For logical volumes, sync_percent and estimated_time are always
434
    None (no recovery in progress, as we don't handle the mirrored LV
435
    case). The is_degraded parameter is the inverse of the ldisk
436
    parameter.
437

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

444
    """
445
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
446
    if result.failed:
447
      logger.Error("Can't display lv: %s - %s" %
448
                   (result.fail_reason, result.output))
449
      return None, None, True, True
450
    out = result.stdout.strip()
451
    # format: type/permissions/alloc/fixed_minor/state/open
452
    if len(out) != 6:
453
      logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
454
      return None, None, True, True
455
    ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
456
                          # backing storage
457
    return None, None, ldisk, ldisk
458

    
459
  def Open(self, force=False):
460
    """Make the device ready for I/O.
461

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

464
    """
465
    pass
466

    
467
  def Close(self):
468
    """Notifies that the device will no longer be used for I/O.
469

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

472
    """
473
    pass
474

    
475
  def Snapshot(self, size):
476
    """Create a snapshot copy of an lvm block device.
477

478
    """
479
    snap_name = self._lv_name + ".snap"
480

    
481
    # remove existing snapshot if found
482
    snap = LogicalVolume((self._vg_name, snap_name), None)
483
    snap.Remove()
484

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

    
496
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
497
                           "-n%s" % snap_name, self.dev_path])
498
    if result.failed:
499
      raise errors.BlockDeviceError("command: %s error: %s - %s" %
500
                                    (result.cmd, result.fail_reason,
501
                                     result.output))
502

    
503
    return snap_name
504

    
505
  def SetInfo(self, text):
506
    """Update metadata with info text.
507

508
    """
509
    BlockDev.SetInfo(self, text)
510

    
511
    # Replace invalid characters
512
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
513
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
514

    
515
    # Only up to 128 characters are allowed
516
    text = text[:128]
517

    
518
    result = utils.RunCmd(["lvchange", "--addtag", text,
519
                           self.dev_path])
520
    if result.failed:
521
      raise errors.BlockDeviceError("Command: %s error: %s - %s" %
522
                                    (result.cmd, result.fail_reason,
523
                                     result.output))
524

    
525

    
526
class MDRaid1(BlockDev):
527
  """raid1 device implemented via md.
528

529
  """
530
  def __init__(self, unique_id, children):
531
    super(MDRaid1, self).__init__(unique_id, children)
532
    self.major = 9
533
    self.Attach()
534

    
535
  def Attach(self):
536
    """Find an array which matches our config and attach to it.
537

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

540
    """
541
    minor = self._FindMDByUUID(self.unique_id)
542
    if minor is not None:
543
      self._SetFromMinor(minor)
544
    else:
545
      self.minor = None
546
      self.dev_path = None
547

    
548
    return (minor is not None)
549

    
550
  @staticmethod
551
  def _GetUsedDevs():
552
    """Compute the list of in-use MD devices.
553

554
    It doesn't matter if the used device have other raid level, just
555
    that they are in use.
556

557
    """
558
    mdstat = open("/proc/mdstat", "r")
559
    data = mdstat.readlines()
560
    mdstat.close()
561

    
562
    used_md = {}
563
    valid_line = re.compile("^md([0-9]+) : .*$")
564
    for line in data:
565
      match = valid_line.match(line)
566
      if match:
567
        md_no = int(match.group(1))
568
        used_md[md_no] = line
569

    
570
    return used_md
571

    
572
  @staticmethod
573
  def _GetDevInfo(minor):
574
    """Get info about a MD device.
575

576
    Currently only uuid is returned.
577

578
    """
579
    result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
580
    if result.failed:
581
      logger.Error("Can't display md: %s - %s" %
582
                   (result.fail_reason, result.output))
583
      return None
584
    retval = {}
585
    for line in result.stdout.splitlines():
586
      line = line.strip()
587
      kv = line.split(" : ", 1)
588
      if kv:
589
        if kv[0] == "UUID":
590
          retval["uuid"] = kv[1].split()[0]
591
        elif kv[0] == "State":
592
          retval["state"] = kv[1].split(", ")
593
    return retval
594

    
595
  @staticmethod
596
  def _FindUnusedMinor():
597
    """Compute an unused MD minor.
598

599
    This code assumes that there are 256 minors only.
600

601
    """
602
    used_md = MDRaid1._GetUsedDevs()
603
    i = 0
604
    while i < 256:
605
      if i not in used_md:
606
        break
607
      i += 1
608
    if i == 256:
609
      logger.Error("Critical: Out of md minor numbers.")
610
      raise errors.BlockDeviceError("Can't find a free MD minor")
611
    return i
612

    
613
  @classmethod
614
  def _FindMDByUUID(cls, uuid):
615
    """Find the minor of an MD array with a given UUID.
616

617
    """
618
    md_list = cls._GetUsedDevs()
619
    for minor in md_list:
620
      info = cls._GetDevInfo(minor)
621
      if info and info["uuid"] == uuid:
622
        return minor
623
    return None
624

    
625
  @staticmethod
626
  def _ZeroSuperblock(dev_path):
627
    """Zero the possible locations for an MD superblock.
628

629
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
630
    fails in versions 2.x with the same error code as non-writable
631
    device.
632

633
    The superblocks are located at (negative values are relative to
634
    the end of the block device):
635
      - -128k to end for version 0.90 superblock
636
      - -8k to -12k for version 1.0 superblock (included in the above)
637
      - 0k to 4k for version 1.1 superblock
638
      - 4k to 8k for version 1.2 superblock
639

640
    To cover all situations, the zero-ing will be:
641
      - 0k to 128k
642
      - -128k to end
643

644
    As such, the minimum device size must be 128k, otherwise we'll get
645
    I/O errors.
646

647
    Note that this function depends on the fact that one can open,
648
    read and write block devices normally.
649

650
    """
651
    overwrite_size = 128 * 1024
652
    empty_buf = '\0' * overwrite_size
653
    fd = open(dev_path, "r+")
654
    try:
655
      fd.seek(0, 0)
656
      p1 = fd.tell()
657
      fd.write(empty_buf)
658
      p2 = fd.tell()
659
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
660
      fd.seek(-overwrite_size, 2)
661
      p1 = fd.tell()
662
      fd.write(empty_buf)
663
      p2 = fd.tell()
664
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
665
    finally:
666
      fd.close()
667

    
668
  @classmethod
669
  def Create(cls, unique_id, children, size):
670
    """Create a new MD raid1 array.
671

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

    
692
    if result.failed:
693
      logger.Error("Can't create md: %s: %s" % (result.fail_reason,
694
                                                result.output))
695
      return None
696
    info = cls._GetDevInfo(minor)
697
    if not info or not "uuid" in info:
698
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
699
      return None
700
    return MDRaid1(info["uuid"], children)
701

    
702
  def Remove(self):
703
    """Stub remove function for MD RAID 1 arrays.
704

705
    We don't remove the superblock right now. Mark a to do.
706

707
    """
708
    #TODO: maybe zero superblock on child devices?
709
    return self.Shutdown()
710

    
711
  def Rename(self, new_id):
712
    """Rename a device.
713

714
    This is not supported for md raid1 devices.
715

716
    """
717
    raise errors.ProgrammerError("Can't rename a md raid1 device")
718

    
719
  def AddChildren(self, devices):
720
    """Add new member(s) to the md raid1.
721

722
    """
723
    if self.minor is None and not self.Attach():
724
      raise errors.BlockDeviceError("Can't attach to device")
725

    
726
    args = ["mdadm", "-a", self.dev_path]
727
    for dev in devices:
728
      if dev.dev_path is None:
729
        raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
730
      dev.Open()
731
      args.append(dev.dev_path)
732
    result = utils.RunCmd(args)
733
    if result.failed:
734
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
735
                                    result.output)
736
    new_len = len(self._children) + len(devices)
737
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
738
    if result.failed:
739
      raise errors.BlockDeviceError("Can't grow md array: %s" %
740
                                    result.output)
741
    self._children.extend(devices)
742

    
743
  def RemoveChildren(self, devices):
744
    """Remove member(s) from the md raid1.
745

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

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

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

787
    This sets our minor variable and our dev_path.
788

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

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

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

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

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

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

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

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

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

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

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

866
    Returns:
867
     (sync_percent, estimated_time, is_degraded, ldisk)
868

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

873
    The ldisk parameter is always true for MD devices.
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, False
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, False
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, False
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
    pass
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
    pass
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+)(?:-(\d+))?\)")
931

    
932
  _DRBD_MAJOR = 147
933
  _ST_UNCONFIGURED = "Unconfigured"
934
  _ST_WFCONNECTION = "WFConnection"
935
  _ST_CONNECTED = "Connected"
936

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

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

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

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

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

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

981
    This will return a dict with keys:
982
      k_major,
983
      k_minor,
984
      k_point,
985
      api,
986
      proto,
987
      proto2 (only on drbd > 8.2.X)
988

989
    """
990
    proc_data = cls._GetProcData()
991
    first_line = proc_data[0].strip()
992
    version = cls._VERSION_RE.match(first_line)
993
    if not version:
994
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
995
                                    first_line)
996

    
997
    values = version.groups()
998
    retval = {'k_major': int(values[0]),
999
              'k_minor': int(values[1]),
1000
              'k_point': int(values[2]),
1001
              'api': int(values[3]),
1002
              'proto': int(values[4]),
1003
             }
1004
    if values[5] is not None:
1005
      retval['proto2'] = values[5]
1006

    
1007
    return retval
1008

    
1009
  @staticmethod
1010
  def _DevPath(minor):
1011
    """Return the path to a drbd device for a given minor.
1012

1013
    """
1014
    return "/dev/drbd%d" % minor
1015

    
1016
  @classmethod
1017
  def _GetUsedDevs(cls):
1018
    """Compute the list of used DRBD devices.
1019

1020
    """
1021
    data = cls._GetProcData()
1022

    
1023
    used_devs = {}
1024
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1025
    for line in data:
1026
      match = valid_line.match(line)
1027
      if not match:
1028
        continue
1029
      minor = int(match.group(1))
1030
      state = match.group(2)
1031
      if state == cls._ST_UNCONFIGURED:
1032
        continue
1033
      used_devs[minor] = state, line
1034

    
1035
    return used_devs
1036

    
1037
  def _SetFromMinor(self, minor):
1038
    """Set our parameters based on the given minor.
1039

1040
    This sets our minor variable and our dev_path.
1041

1042
    """
1043
    if minor is None:
1044
      self.minor = self.dev_path = None
1045
    else:
1046
      self.minor = minor
1047
      self.dev_path = self._DevPath(minor)
1048

    
1049
  @staticmethod
1050
  def _CheckMetaSize(meta_device):
1051
    """Check if the given meta device looks like a valid one.
1052

1053
    This currently only check the size, which must be around
1054
    128MiB.
1055

1056
    """
1057
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1058
    if result.failed:
1059
      logger.Error("Failed to get device size: %s - %s" %
1060
                   (result.fail_reason, result.output))
1061
      return False
1062
    try:
1063
      sectors = int(result.stdout)
1064
    except ValueError:
1065
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1066
      return False
1067
    bytes = sectors * 512
1068
    if bytes < 128 * 1024 * 1024: # less than 128MiB
1069
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1070
      return False
1071
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1072
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1073
      return False
1074
    return True
1075

    
1076
  def Rename(self, new_id):
1077
    """Rename a device.
1078

1079
    This is not supported for drbd devices.
1080

1081
    """
1082
    raise errors.ProgrammerError("Can't rename a drbd device")
1083

    
1084

    
1085
class DRBDev(BaseDRBD):
1086
  """DRBD block device.
1087

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

1092
  The unique_id for the drbd device is the (local_ip, local_port,
1093
  remote_ip, remote_port) tuple, and it must have two children: the
1094
  data device and the meta_device. The meta device is checked for
1095
  valid size and is zeroed on create.
1096

1097
  """
1098
  def __init__(self, unique_id, children):
1099
    super(DRBDev, self).__init__(unique_id, children)
1100
    self.major = self._DRBD_MAJOR
1101
    version = self._GetVersion()
1102
    if version['k_major'] != 0 and version['k_minor'] != 7:
1103
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1104
                                    " requested ganeti usage: kernel is"
1105
                                    " %s.%s, ganeti wants 0.7" %
1106
                                    (version['k_major'], version['k_minor']))
1107
    if len(children) != 2:
1108
      raise ValueError("Invalid configuration data %s" % str(children))
1109
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1110
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1111
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1112
    self.Attach()
1113

    
1114
  @classmethod
1115
  def _FindUnusedMinor(cls):
1116
    """Find an unused DRBD device.
1117

1118
    """
1119
    data = cls._GetProcData()
1120

    
1121
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1122
    for line in data:
1123
      match = valid_line.match(line)
1124
      if match:
1125
        return int(match.group(1))
1126
    logger.Error("Error: no free drbd minors!")
1127
    raise errors.BlockDeviceError("Can't find a free DRBD minor")
1128

    
1129
  @classmethod
1130
  def _GetDevInfo(cls, minor):
1131
    """Get details about a given DRBD minor.
1132

1133
    This return, if available, the local backing device in (major,
1134
    minor) formant and the local and remote (ip, port) information.
1135

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

    
1181
  def _MatchesLocal(self, info):
1182
    """Test if our local config matches with an existing device.
1183

1184
    The parameter should be as returned from `_GetDevInfo()`. This
1185
    method tests if our local backing device is the same as the one in
1186
    the info parameter, in effect testing if we look like the given
1187
    device.
1188

1189
    """
1190
    if not ("local_dev" in info and "meta_dev" in info and
1191
            "meta_index" in info):
1192
      return False
1193

    
1194
    backend = self._children[0]
1195
    if backend is not None:
1196
      retval = (info["local_dev"] == (backend.major, backend.minor))
1197
    else:
1198
      retval = (info["local_dev"] == (0, 0))
1199
    meta = self._children[1]
1200
    if meta is not None:
1201
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1202
      retval = retval and (info["meta_index"] == 0)
1203
    else:
1204
      retval = retval and (info["meta_dev"] == "internal" and
1205
                           info["meta_index"] == -1)
1206
    return retval
1207

    
1208
  def _MatchesNet(self, info):
1209
    """Test if our network config matches with an existing device.
1210

1211
    The parameter should be as returned from `_GetDevInfo()`. This
1212
    method tests if our network configuration is the same as the one
1213
    in the info parameter, in effect testing if we look like the given
1214
    device.
1215

1216
    """
1217
    if (((self._lhost is None and not ("local_addr" in info)) and
1218
         (self._rhost is None and not ("remote_addr" in info)))):
1219
      return True
1220

    
1221
    if self._lhost is None:
1222
      return False
1223

    
1224
    if not ("local_addr" in info and
1225
            "remote_addr" in info):
1226
      return False
1227

    
1228
    retval = (info["local_addr"] == (self._lhost, self._lport))
1229
    retval = (retval and
1230
              info["remote_addr"] == (self._rhost, self._rport))
1231
    return retval
1232

    
1233
  @classmethod
1234
  def _AssembleLocal(cls, minor, backend, meta):
1235
    """Configure the local part of a DRBD device.
1236

1237
    This is the first thing that must be done on an unconfigured DRBD
1238
    device. And it must be done only once.
1239

1240
    """
1241
    if not cls._CheckMetaSize(meta):
1242
      return False
1243
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1244
                           backend, meta, "0", "-e", "detach"])
1245
    if result.failed:
1246
      logger.Error("Can't attach local disk: %s" % result.output)
1247
    return not result.failed
1248

    
1249
  @classmethod
1250
  def _ShutdownLocal(cls, minor):
1251
    """Detach from the local device.
1252

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

1256
    """
1257
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1258
    if result.failed:
1259
      logger.Error("Can't detach local device: %s" % result.output)
1260
    return not result.failed
1261

    
1262
  @staticmethod
1263
  def _ShutdownAll(minor):
1264
    """Deactivate the device.
1265

1266
    This will, of course, fail if the device is in use.
1267

1268
    """
1269
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1270
    if result.failed:
1271
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1272
    return not result.failed
1273

    
1274
  @classmethod
1275
  def _AssembleNet(cls, minor, net_info, protocol):
1276
    """Configure the network part of the device.
1277

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

1285
    """
1286
    lhost, lport, rhost, rport = net_info
1287
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1288
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1289
                           protocol])
1290
    if result.failed:
1291
      logger.Error("Can't setup network for dbrd device: %s - %s" %
1292
                   (result.fail_reason, result.output))
1293
      return False
1294

    
1295
    timeout = time.time() + 10
1296
    ok = False
1297
    while time.time() < timeout:
1298
      info = cls._GetDevInfo(minor)
1299
      if not "local_addr" in info or not "remote_addr" in info:
1300
        time.sleep(1)
1301
        continue
1302
      if (info["local_addr"] != (lhost, lport) or
1303
          info["remote_addr"] != (rhost, rport)):
1304
        time.sleep(1)
1305
        continue
1306
      ok = True
1307
      break
1308
    if not ok:
1309
      logger.Error("Timeout while configuring network")
1310
      return False
1311
    return True
1312

    
1313
  @classmethod
1314
  def _ShutdownNet(cls, minor):
1315
    """Disconnect from the remote peer.
1316

1317
    This fails if we don't have a local device.
1318

1319
    """
1320
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1321
    if result.failed:
1322
      logger.Error("Can't shutdown network: %s" % result.output)
1323
    return not result.failed
1324

    
1325
  def Assemble(self):
1326
    """Assemble the drbd.
1327

1328
    Method:
1329
      - if we have a local backing device, we bind to it by:
1330
        - checking the list of used drbd devices
1331
        - check if the local minor use of any of them is our own device
1332
        - if yes, abort?
1333
        - if not, bind
1334
      - if we have a local/remote net info:
1335
        - redo the local backing device step for the remote device
1336
        - check if any drbd device is using the local port,
1337
          if yes abort
1338
        - check if any remote drbd device is using the remote
1339
          port, if yes abort (for now)
1340
        - bind our net port
1341
        - bind the remote net port
1342

1343
    """
1344
    self.Attach()
1345
    if self.minor is not None:
1346
      logger.Info("Already assembled")
1347
      return True
1348

    
1349
    result = super(DRBDev, self).Assemble()
1350
    if not result:
1351
      return result
1352

    
1353
    minor = self._FindUnusedMinor()
1354
    need_localdev_teardown = False
1355
    if self._children[0]:
1356
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1357
                                   self._children[1].dev_path)
1358
      if not result:
1359
        return False
1360
      need_localdev_teardown = True
1361
    if self._lhost and self._lport and self._rhost and self._rport:
1362
      result = self._AssembleNet(minor,
1363
                                 (self._lhost, self._lport,
1364
                                  self._rhost, self._rport),
1365
                                 "C")
1366
      if not result:
1367
        if need_localdev_teardown:
1368
          # we will ignore failures from this
1369
          logger.Error("net setup failed, tearing down local device")
1370
          self._ShutdownAll(minor)
1371
        return False
1372
    self._SetFromMinor(minor)
1373
    return True
1374

    
1375
  def Shutdown(self):
1376
    """Shutdown the DRBD device.
1377

1378
    """
1379
    if self.minor is None and not self.Attach():
1380
      logger.Info("DRBD device not attached to a device during Shutdown")
1381
      return True
1382
    if not self._ShutdownAll(self.minor):
1383
      return False
1384
    self.minor = None
1385
    self.dev_path = None
1386
    return True
1387

    
1388
  def Attach(self):
1389
    """Find a DRBD device which matches our config and attach to it.
1390

1391
    In case of partially attached (local device matches but no network
1392
    setup), we perform the network attach. If successful, we re-test
1393
    the attach if can return success.
1394

1395
    """
1396
    for minor in self._GetUsedDevs():
1397
      info = self._GetDevInfo(minor)
1398
      match_l = self._MatchesLocal(info)
1399
      match_r = self._MatchesNet(info)
1400
      if match_l and match_r:
1401
        break
1402
      if match_l and not match_r and "local_addr" not in info:
1403
        res_r = self._AssembleNet(minor,
1404
                                  (self._lhost, self._lport,
1405
                                   self._rhost, self._rport),
1406
                                  "C")
1407
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1408
          break
1409
    else:
1410
      minor = None
1411

    
1412
    self._SetFromMinor(minor)
1413
    return minor is not None
1414

    
1415
  def Open(self, force=False):
1416
    """Make the local state primary.
1417

1418
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1419
    is given. Since this is a pottentialy dangerous operation, the
1420
    force flag should be only given after creation, when it actually
1421
    has to be given.
1422

1423
    """
1424
    if self.minor is None and not self.Attach():
1425
      logger.Error("DRBD cannot attach to a device during open")
1426
      return False
1427
    cmd = ["drbdsetup", self.dev_path, "primary"]
1428
    if force:
1429
      cmd.append("--do-what-I-say")
1430
    result = utils.RunCmd(cmd)
1431
    if result.failed:
1432
      msg = ("Can't make drbd device primary: %s" % result.output)
1433
      logger.Error(msg)
1434
      raise errors.BlockDeviceError(msg)
1435

    
1436
  def Close(self):
1437
    """Make the local state secondary.
1438

1439
    This will, of course, fail if the device is in use.
1440

1441
    """
1442
    if self.minor is None and not self.Attach():
1443
      logger.Info("Instance not attached to a device")
1444
      raise errors.BlockDeviceError("Can't find device")
1445
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1446
    if result.failed:
1447
      msg = ("Can't switch drbd device to"
1448
             " secondary: %s" % result.output)
1449
      logger.Error(msg)
1450
      raise errors.BlockDeviceError(msg)
1451

    
1452
  def SetSyncSpeed(self, kbytes):
1453
    """Set the speed of the DRBD syncer.
1454

1455
    """
1456
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1457
    if self.minor is None:
1458
      logger.Info("Instance not attached to a device")
1459
      return False
1460
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1461
                           kbytes])
1462
    if result.failed:
1463
      logger.Error("Can't change syncer rate: %s - %s" %
1464
                   (result.fail_reason, result.output))
1465
    return not result.failed and children_result
1466

    
1467
  def GetSyncStatus(self):
1468
    """Returns the sync status of the device.
1469

1470
    Returns:
1471
     (sync_percent, estimated_time, is_degraded, ldisk)
1472

1473
    If sync_percent is None, it means all is ok
1474
    If estimated_time is None, it means we can't esimate
1475
    the time needed, otherwise it's the time left in seconds.
1476

1477
    The ldisk parameter will be returned as True, since the DRBD7
1478
    devices have not been converted.
1479

1480
    """
1481
    if self.minor is None and not self.Attach():
1482
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1483
    proc_info = self._MassageProcData(self._GetProcData())
1484
    if self.minor not in proc_info:
1485
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1486
                                    self.minor)
1487
    line = proc_info[self.minor]
1488
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1489
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1490
    if match:
1491
      sync_percent = float(match.group(1))
1492
      hours = int(match.group(2))
1493
      minutes = int(match.group(3))
1494
      seconds = int(match.group(4))
1495
      est_time = hours * 3600 + minutes * 60 + seconds
1496
    else:
1497
      sync_percent = None
1498
      est_time = None
1499
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1500
    if not match:
1501
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1502
                                    self.minor)
1503
    client_state = match.group(1)
1504
    is_degraded = client_state != "Connected"
1505
    return sync_percent, est_time, is_degraded, False
1506

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

1511
    This writes until we get ENOSPC.
1512

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

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

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

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

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

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

    
1550

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1688
    cls._PARSE_SHOW = bnf
1689

    
1690
    return bnf
1691

    
1692
  @classmethod
1693
  def _GetShowData(cls, minor):
1694
    """Return the `drbdsetup show` data for a minor.
1695

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

    
1704
  @classmethod
1705
  def _GetDevInfo(cls, out):
1706
    """Parse details about a given DRBD minor.
1707

1708
    This return, if available, the local backing device (as a path)
1709
    and the local and remote (ip, port) information from a string
1710
    containing the output of the `drbdsetup show` command as returned
1711
    by _GetShowData.
1712

1713
    """
1714
    data = {}
1715
    if not out:
1716
      return data
1717

    
1718
    bnf = cls._GetShowParser()
1719
    # run pyparse
1720

    
1721
    try:
1722
      results = bnf.parseString(out)
1723
    except pyp.ParseException, err:
1724
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1725
                                    str(err))
1726

    
1727
    # and massage the results into our desired format
1728
    for section in results:
1729
      sname = section[0]
1730
      if sname == "_this_host":
1731
        for lst in section[1:]:
1732
          if lst[0] == "disk":
1733
            data["local_dev"] = lst[1]
1734
          elif lst[0] == "meta-disk":
1735
            data["meta_dev"] = lst[1]
1736
            data["meta_index"] = lst[2]
1737
          elif lst[0] == "address":
1738
            data["local_addr"] = tuple(lst[1:])
1739
      elif sname == "_remote_host":
1740
        for lst in section[1:]:
1741
          if lst[0] == "address":
1742
            data["remote_addr"] = tuple(lst[1:])
1743
    return data
1744

    
1745
  def _MatchesLocal(self, info):
1746
    """Test if our local config matches with an existing device.
1747

1748
    The parameter should be as returned from `_GetDevInfo()`. This
1749
    method tests if our local backing device is the same as the one in
1750
    the info parameter, in effect testing if we look like the given
1751
    device.
1752

1753
    """
1754
    if self._children:
1755
      backend, meta = self._children
1756
    else:
1757
      backend = meta = None
1758

    
1759
    if backend is not None:
1760
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1761
    else:
1762
      retval = ("local_dev" not in info)
1763

    
1764
    if meta is not None:
1765
      retval = retval and ("meta_dev" in info and
1766
                           info["meta_dev"] == meta.dev_path)
1767
      retval = retval and ("meta_index" in info and
1768
                           info["meta_index"] == 0)
1769
    else:
1770
      retval = retval and ("meta_dev" not in info and
1771
                           "meta_index" not in info)
1772
    return retval
1773

    
1774
  def _MatchesNet(self, info):
1775
    """Test if our network config matches with an existing device.
1776

1777
    The parameter should be as returned from `_GetDevInfo()`. This
1778
    method tests if our network configuration is the same as the one
1779
    in the info parameter, in effect testing if we look like the given
1780
    device.
1781

1782
    """
1783
    if (((self._lhost is None and not ("local_addr" in info)) and
1784
         (self._rhost is None and not ("remote_addr" in info)))):
1785
      return True
1786

    
1787
    if self._lhost is None:
1788
      return False
1789

    
1790
    if not ("local_addr" in info and
1791
            "remote_addr" in info):
1792
      return False
1793

    
1794
    retval = (info["local_addr"] == (self._lhost, self._lport))
1795
    retval = (retval and
1796
              info["remote_addr"] == (self._rhost, self._rport))
1797
    return retval
1798

    
1799
  @classmethod
1800
  def _AssembleLocal(cls, minor, backend, meta):
1801
    """Configure the local part of a DRBD device.
1802

1803
    This is the first thing that must be done on an unconfigured DRBD
1804
    device. And it must be done only once.
1805

1806
    """
1807
    if not cls._IsValidMeta(meta):
1808
      return False
1809
    args = ["drbdsetup", cls._DevPath(minor), "disk",
1810
            backend, meta, "0", "-e", "detach", "--create-device"]
1811
    result = utils.RunCmd(args)
1812
    if result.failed:
1813
      logger.Error("Can't attach local disk: %s" % result.output)
1814
    return not result.failed
1815

    
1816
  @classmethod
1817
  def _AssembleNet(cls, minor, net_info, protocol,
1818
                   dual_pri=False, hmac=None, secret=None):
1819
    """Configure the network part of the device.
1820

1821
    """
1822
    lhost, lport, rhost, rport = net_info
1823
    if None in net_info:
1824
      # we don't want network connection and actually want to make
1825
      # sure its shutdown
1826
      return cls._ShutdownNet(minor)
1827

    
1828
    args = ["drbdsetup", cls._DevPath(minor), "net",
1829
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1830
            "-A", "discard-zero-changes",
1831
            "-B", "consensus",
1832
            "--create-device",
1833
            ]
1834
    if dual_pri:
1835
      args.append("-m")
1836
    if hmac and secret:
1837
      args.extend(["-a", hmac, "-x", secret])
1838
    result = utils.RunCmd(args)
1839
    if result.failed:
1840
      logger.Error("Can't setup network for dbrd device: %s - %s" %
1841
                   (result.fail_reason, result.output))
1842
      return False
1843

    
1844
    timeout = time.time() + 10
1845
    ok = False
1846
    while time.time() < timeout:
1847
      info = cls._GetDevInfo(cls._GetShowData(minor))
1848
      if not "local_addr" in info or not "remote_addr" in info:
1849
        time.sleep(1)
1850
        continue
1851
      if (info["local_addr"] != (lhost, lport) or
1852
          info["remote_addr"] != (rhost, rport)):
1853
        time.sleep(1)
1854
        continue
1855
      ok = True
1856
      break
1857
    if not ok:
1858
      logger.Error("Timeout while configuring network")
1859
      return False
1860
    return True
1861

    
1862
  def AddChildren(self, devices):
1863
    """Add a disk to the DRBD device.
1864

1865
    """
1866
    if self.minor is None:
1867
      raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1868
    if len(devices) != 2:
1869
      raise errors.BlockDeviceError("Need two devices for AddChildren")
1870
    info = self._GetDevInfo(self._GetShowData(self.minor))
1871
    if "local_dev" in info:
1872
      raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1873
    backend, meta = devices
1874
    if backend.dev_path is None or meta.dev_path is None:
1875
      raise errors.BlockDeviceError("Children not ready during AddChildren")
1876
    backend.Open()
1877
    meta.Open()
1878
    if not self._CheckMetaSize(meta.dev_path):
1879
      raise errors.BlockDeviceError("Invalid meta device size")
1880
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1881
    if not self._IsValidMeta(meta.dev_path):
1882
      raise errors.BlockDeviceError("Cannot initalize meta device")
1883

    
1884
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1885
      raise errors.BlockDeviceError("Can't attach to local storage")
1886
    self._children = devices
1887

    
1888
  def RemoveChildren(self, devices):
1889
    """Detach the drbd device from local storage.
1890

1891
    """
1892
    if self.minor is None:
1893
      raise errors.BlockDeviceError("Can't attach to drbd8 during"
1894
                                    " RemoveChildren")
1895
    # early return if we don't actually have backing storage
1896
    info = self._GetDevInfo(self._GetShowData(self.minor))
1897
    if "local_dev" not in info:
1898
      return
1899
    if len(self._children) != 2:
1900
      raise errors.BlockDeviceError("We don't have two children: %s" %
1901
                                    self._children)
1902
    if self._children.count(None) == 2: # we don't actually have children :)
1903
      logger.Error("Requested detach while detached")
1904
      return
1905
    if len(devices) != 2:
1906
      raise errors.BlockDeviceError("We need two children in RemoveChildren")
1907
    for child, dev in zip(self._children, devices):
1908
      if dev != child.dev_path:
1909
        raise errors.BlockDeviceError("Mismatch in local storage"
1910
                                      " (%s != %s) in RemoveChildren" %
1911
                                      (dev, child.dev_path))
1912

    
1913
    if not self._ShutdownLocal(self.minor):
1914
      raise errors.BlockDeviceError("Can't detach from local storage")
1915
    self._children = []
1916

    
1917
  def SetSyncSpeed(self, kbytes):
1918
    """Set the speed of the DRBD syncer.
1919

1920
    """
1921
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1922
    if self.minor is None:
1923
      logger.Info("Instance not attached to a device")
1924
      return False
1925
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1926
                           kbytes])
1927
    if result.failed:
1928
      logger.Error("Can't change syncer rate: %s - %s" %
1929
                   (result.fail_reason, result.output))
1930
    return not result.failed and children_result
1931

    
1932
  def GetSyncStatus(self):
1933
    """Returns the sync status of the device.
1934

1935
    Returns:
1936
     (sync_percent, estimated_time, is_degraded)
1937

1938
    If sync_percent is None, it means all is ok
1939
    If estimated_time is None, it means we can't esimate
1940
    the time needed, otherwise it's the time left in seconds.
1941

1942

1943
    We set the is_degraded parameter to True on two conditions:
1944
    network not connected or local disk missing.
1945

1946
    We compute the ldisk parameter based on wheter we have a local
1947
    disk or not.
1948

1949
    """
1950
    if self.minor is None and not self.Attach():
1951
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1952
    proc_info = self._MassageProcData(self._GetProcData())
1953
    if self.minor not in proc_info:
1954
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1955
                                    self.minor)
1956
    line = proc_info[self.minor]
1957
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1958
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1959
    if match:
1960
      sync_percent = float(match.group(1))
1961
      hours = int(match.group(2))
1962
      minutes = int(match.group(3))
1963
      seconds = int(match.group(4))
1964
      est_time = hours * 3600 + minutes * 60 + seconds
1965
    else:
1966
      sync_percent = None
1967
      est_time = None
1968
    match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1969
    if not match:
1970
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1971
                                    self.minor)
1972
    client_state = match.group(1)
1973
    local_disk_state = match.group(2)
1974
    ldisk = local_disk_state != "UpToDate"
1975
    is_degraded = client_state != "Connected"
1976
    return sync_percent, est_time, is_degraded or ldisk, ldisk
1977

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

1981
    If the 'force' parameter is given, the '-o' option is passed to
1982
    drbdsetup. Since this is a potentially dangerous operation, the
1983
    force flag should be only given after creation, when it actually
1984
    is mandatory.
1985

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

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

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

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

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

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

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

    
2042
      # this case must be considered only if we actually have local
2043
      # storage, i.e. not in diskless mode, because all diskless
2044
      # devices are equal from the point of view of local
2045
      # configuration
2046
      if (match_l and "local_dev" in info and
2047
          not match_r and "local_addr" in info):
2048
        # strange case - the device network part points to somewhere
2049
        # else, even though its local storage is ours; as we own the
2050
        # drbd space, we try to disconnect from the remote peer and
2051
        # reconnect to our correct one
2052
        if not self._ShutdownNet(minor):
2053
          raise errors.BlockDeviceError("Device has correct local storage,"
2054
                                        " wrong remote peer and is unable to"
2055
                                        " disconnect in order to attach to"
2056
                                        " the correct peer")
2057
        # note: _AssembleNet also handles the case when we don't want
2058
        # local storage (i.e. one or more of the _[lr](host|port) is
2059
        # None)
2060
        if (self._AssembleNet(minor, (self._lhost, self._lport,
2061
                                      self._rhost, self._rport), "C") and
2062
            self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2063
          break
2064

    
2065
    else:
2066
      minor = None
2067

    
2068
    self._SetFromMinor(minor)
2069
    return minor is not None
2070

    
2071
  def Assemble(self):
2072
    """Assemble the drbd.
2073

2074
    Method:
2075
      - if we have a local backing device, we bind to it by:
2076
        - checking the list of used drbd devices
2077
        - check if the local minor use of any of them is our own device
2078
        - if yes, abort?
2079
        - if not, bind
2080
      - if we have a local/remote net info:
2081
        - redo the local backing device step for the remote device
2082
        - check if any drbd device is using the local port,
2083
          if yes abort
2084
        - check if any remote drbd device is using the remote
2085
          port, if yes abort (for now)
2086
        - bind our net port
2087
        - bind the remote net port
2088

2089
    """
2090
    self.Attach()
2091
    if self.minor is not None:
2092
      logger.Info("Already assembled")
2093
      return True
2094

    
2095
    result = super(DRBD8, self).Assemble()
2096
    if not result:
2097
      return result
2098

    
2099
    minor = self._FindUnusedMinor()
2100
    need_localdev_teardown = False
2101
    if self._children and self._children[0] and self._children[1]:
2102
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2103
                                   self._children[1].dev_path)
2104
      if not result:
2105
        return False
2106
      need_localdev_teardown = True
2107
    if self._lhost and self._lport and self._rhost and self._rport:
2108
      result = self._AssembleNet(minor,
2109
                                 (self._lhost, self._lport,
2110
                                  self._rhost, self._rport),
2111
                                 "C")
2112
      if not result:
2113
        if need_localdev_teardown:
2114
          # we will ignore failures from this
2115
          logger.Error("net setup failed, tearing down local device")
2116
          self._ShutdownAll(minor)
2117
        return False
2118
    self._SetFromMinor(minor)
2119
    return True
2120

    
2121
  @classmethod
2122
  def _ShutdownLocal(cls, minor):
2123
    """Detach from the local device.
2124

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

2128
    """
2129
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2130
    if result.failed:
2131
      logger.Error("Can't detach local device: %s" % result.output)
2132
    return not result.failed
2133

    
2134
  @classmethod
2135
  def _ShutdownNet(cls, minor):
2136
    """Disconnect from the remote peer.
2137

2138
    This fails if we don't have a local device.
2139

2140
    """
2141
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2142
    if result.failed:
2143
      logger.Error("Can't shutdown network: %s" % result.output)
2144
    return not result.failed
2145

    
2146
  @classmethod
2147
  def _ShutdownAll(cls, minor):
2148
    """Deactivate the device.
2149

2150
    This will, of course, fail if the device is in use.
2151

2152
    """
2153
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2154
    if result.failed:
2155
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2156
    return not result.failed
2157

    
2158
  def Shutdown(self):
2159
    """Shutdown the DRBD device.
2160

2161
    """
2162
    if self.minor is None and not self.Attach():
2163
      logger.Info("DRBD device not attached to a device during Shutdown")
2164
      return True
2165
    if not self._ShutdownAll(self.minor):
2166
      return False
2167
    self.minor = None
2168
    self.dev_path = None
2169
    return True
2170

    
2171
  def Remove(self):
2172
    """Stub remove for DRBD devices.
2173

2174
    """
2175
    return self.Shutdown()
2176

    
2177
  @classmethod
2178
  def Create(cls, unique_id, children, size):
2179
    """Create a new DRBD8 device.
2180

2181
    Since DRBD devices are not created per se, just assembled, this
2182
    function only initializes the metadata.
2183

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

    
2198

    
2199
class FileStorage(BlockDev):
2200
  """File device.
2201
  
2202
  This class represents the a file storage backend device.
2203

2204
  The unique_id for the file device is a (file_driver, file_path) tuple.
2205
  
2206
  """
2207
  def __init__(self, unique_id, children):
2208
    """Initalizes a file device backend.
2209

2210
    """
2211
    if children:
2212
      raise errors.BlockDeviceError("Invalid setup for file device")
2213
    super(FileStorage, self).__init__(unique_id, children)
2214
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2215
      raise ValueError("Invalid configuration data %s" % str(unique_id))
2216
    self.driver = unique_id[0]
2217
    self.dev_path = unique_id[1]
2218

    
2219
  def Assemble(self):
2220
    """Assemble the device.
2221

2222
    Checks whether the file device exists, raises BlockDeviceError otherwise.
2223

2224
    """
2225
    if not os.path.exists(self.dev_path):
2226
      raise errors.BlockDeviceError("File device '%s' does not exist." %
2227
                                    self.dev_path)
2228
    return True
2229

    
2230
  def Shutdown(self):
2231
    """Shutdown the device.
2232

2233
    This is a no-op for the file type, as we don't deacivate
2234
    the file on shutdown.
2235

2236
    """
2237
    return True
2238

    
2239
  def Open(self, force=False):
2240
    """Make the device ready for I/O.
2241

2242
    This is a no-op for the file type.
2243

2244
    """
2245
    pass
2246

    
2247
  def Close(self):
2248
    """Notifies that the device will no longer be used for I/O.
2249

2250
    This is a no-op for the file type.
2251

2252
    """
2253
    pass
2254

    
2255
  def Remove(self):
2256
    """Remove the file backing the block device.
2257

2258
    Returns:
2259
      boolean indicating wheter removal of file was successful or not.
2260

2261
    """
2262
    if not os.path.exists(self.dev_path):
2263
      return True
2264
    try:
2265
      os.remove(self.dev_path)
2266
      return True
2267
    except OSError, err:
2268
      logger.Error("Can't remove file '%s': %s"
2269
                   % (self.dev_path, err))
2270
      return False
2271

    
2272
  def Attach(self):
2273
    """Attach to an existing file.
2274

2275
    Check if this file already exists.
2276

2277
    Returns:
2278
      boolean indicating if file exists or not.
2279

2280
    """
2281
    if os.path.exists(self.dev_path):
2282
      return True
2283
    return False
2284

    
2285
  @classmethod
2286
  def Create(cls, unique_id, children, size):
2287
    """Create a new file.
2288

2289
    Args:
2290
      children:
2291
      size: integer size of file in MiB
2292

2293
    Returns:
2294
      A ganeti.bdev.FileStorage object.
2295

2296
    """
2297
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2298
      raise ValueError("Invalid configuration data %s" % str(unique_id))
2299
    dev_path = unique_id[1]
2300
    try:
2301
      f = open(dev_path, 'w')
2302
    except IOError, err:
2303
      raise errors.BlockDeviceError("Could not create '%'" % err)
2304
    else:
2305
      f.truncate(size * 1024 * 1024)
2306
      f.close()
2307

    
2308
    return FileStorage(unique_id, children)
2309

    
2310

    
2311
DEV_MAP = {
2312
  constants.LD_LV: LogicalVolume,
2313
  constants.LD_MD_R1: MDRaid1,
2314
  constants.LD_DRBD7: DRBDev,
2315
  constants.LD_DRBD8: DRBD8,
2316
  constants.LD_FILE: FileStorage,
2317
  }
2318

    
2319

    
2320
def FindDevice(dev_type, unique_id, children):
2321
  """Search for an existing, assembled device.
2322

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

2326
  """
2327
  if dev_type not in DEV_MAP:
2328
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2329
  device = DEV_MAP[dev_type](unique_id, children)
2330
  if not device.Attach():
2331
    return None
2332
  return  device
2333

    
2334

    
2335
def AttachOrAssemble(dev_type, unique_id, children):
2336
  """Try to attach or assemble an existing device.
2337

2338
  This will attach to an existing assembled device or will assemble
2339
  the device, as needed, to bring it fully up.
2340

2341
  """
2342
  if dev_type not in DEV_MAP:
2343
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2344
  device = DEV_MAP[dev_type](unique_id, children)
2345
  if not device.Attach():
2346
    device.Assemble()
2347
    if not device.Attach():
2348
      raise errors.BlockDeviceError("Can't find a valid block device for"
2349
                                    " %s/%s/%s" %
2350
                                    (dev_type, unique_id, children))
2351
  return device
2352

    
2353

    
2354
def Create(dev_type, unique_id, children, size):
2355
  """Create a device.
2356

2357
  """
2358
  if dev_type not in DEV_MAP:
2359
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2360
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2361
  return device