Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ d6646186

History | View | Annotate | Download (73.3 kB)

1
#
2
#
3

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

    
21

    
22
"""Block device abstraction"""
23

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

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

    
34

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
126
      try:
127
        child.Open()
128
      except errors.BlockDeviceError:
129
        for child in self._children:
130
          child.Shutdown()
131
        raise
132

    
133
    if not status:
134
      for child in self._children:
135
        child.Shutdown()
136
    return status
137

    
138
  def Attach(self):
139
    """Find a device which matches our config and attach to it.
140

141
    """
142
    raise NotImplementedError
143

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

147
    """
148
    raise NotImplementedError
149

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

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

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

161
    """
162
    raise NotImplementedError
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
  def Rename(self, new_id):
175
    """Rename this device.
176

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

179
    """
180
    raise NotImplementedError
181

    
182
  def GetStatus(self):
183
    """Return the status of the device.
184

185
    """
186
    raise NotImplementedError
187

    
188
  def Open(self, force=False):
189
    """Make the device ready for use.
190

191
    This makes the device ready for I/O. For now, just the DRBD
192
    devices need this.
193

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

197
    """
198
    raise NotImplementedError
199

    
200
  def Shutdown(self):
201
    """Shut down the device, freeing its children.
202

203
    This undoes the `Assemble()` work, except for the child
204
    assembling; as such, the children on the device are still
205
    assembled after this call.
206

207
    """
208
    raise NotImplementedError
209

    
210
  def SetSyncSpeed(self, speed):
211
    """Adjust the sync speed of the mirror.
212

213
    In case this is not a mirroring device, this is no-op.
214

215
    """
216
    result = True
217
    if self._children:
218
      for child in self._children:
219
        result = result and child.SetSyncSpeed(speed)
220
    return result
221

    
222
  def GetSyncStatus(self):
223
    """Returns the sync status of the device.
224

225
    If this device is a mirroring device, this function returns the
226
    status of the mirror.
227

228
    Returns:
229
     (sync_percent, estimated_time, is_degraded, ldisk)
230

231
    If sync_percent is None, it means the device is not syncing.
232

233
    If estimated_time is None, it means we can't estimate
234
    the time needed, otherwise it's the time left in seconds.
235

236
    If is_degraded is True, it means the device is missing
237
    redundancy. This is usually a sign that something went wrong in
238
    the device setup, if sync_percent is None.
239

240
    The ldisk parameter represents the degradation of the local
241
    data. This is only valid for some devices, the rest will always
242
    return False (not degraded).
243

244
    """
245
    return None, None, False, 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, ldisk = self.GetSyncStatus()
257
    if self._children:
258
      for child in self._children:
259
        c_percent, c_time, c_degraded, c_ldisk = 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
        ldisk = ldisk or c_ldisk
270
    return min_percent, max_time, is_degraded, ldisk
271

    
272

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

276
    Only supported for some device types.
277

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

    
282

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

    
288

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

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

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

298
    """
299
    super(LogicalVolume, self).__init__(unique_id, children)
300
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
301
      raise ValueError("Invalid configuration data %s" % str(unique_id))
302
    self._vg_name, self._lv_name = unique_id
303
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
304
    self.Attach()
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
    self._lv_name = new_name
396
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
397

    
398

    
399
  def Attach(self):
400
    """Attach to an existing LV.
401

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

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

    
421
  def Assemble(self):
422
    """Assemble the device.
423

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

428
    """
429
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
430
    if result.failed:
431
      logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
432
    return not result.failed
433

    
434
  def Shutdown(self):
435
    """Shutdown the device.
436

437
    This is a no-op for the LV device type, as we don't deactivate the
438
    volumes on shutdown.
439

440
    """
441
    return True
442

    
443
  def GetStatus(self):
444
    """Return the status of the device.
445

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

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

    
469
    return retval
470

    
471
  def GetSyncStatus(self):
472
    """Returns the sync status of the device.
473

474
    If this device is a mirroring device, this function returns the
475
    status of the mirror.
476

477
    Returns:
478
     (sync_percent, estimated_time, is_degraded, ldisk)
479

480
    For logical volumes, sync_percent and estimated_time are always
481
    None (no recovery in progress, as we don't handle the mirrored LV
482
    case). The is_degraded parameter is the inverse of the ldisk
483
    parameter.
484

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

491
    """
492
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
493
    if result.failed:
494
      logger.Error("Can't display lv: %s" % result.fail_reason)
495
      return None, None, True, True
496
    out = result.stdout.strip()
497
    # format: type/permissions/alloc/fixed_minor/state/open
498
    if len(out) != 6:
499
      logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
500
      return None, None, True, True
501
    ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
502
                          # backing storage
503
    return None, None, ldisk, ldisk
504

    
505
  def Open(self, force=False):
506
    """Make the device ready for I/O.
507

508
    This is a no-op for the LV device type.
509

510
    """
511
    pass
512

    
513
  def Close(self):
514
    """Notifies that the device will no longer be used for I/O.
515

516
    This is a no-op for the LV device type.
517

518
    """
519
    pass
520

    
521
  def Snapshot(self, size):
522
    """Create a snapshot copy of an lvm block device.
523

524
    """
525
    snap_name = self._lv_name + ".snap"
526

    
527
    # remove existing snapshot if found
528
    snap = LogicalVolume((self._vg_name, snap_name), None)
529
    snap.Remove()
530

    
531
    pvs_info = self.GetPVInfo(self._vg_name)
532
    if not pvs_info:
533
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
534
                                    self._vg_name)
535
    pvs_info.sort()
536
    pvs_info.reverse()
537
    free_size, pv_name = pvs_info[0]
538
    if free_size < size:
539
      raise errors.BlockDeviceError("Not enough free space: required %s,"
540
                                    " available %s" % (size, free_size))
541

    
542
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
543
                           "-n%s" % snap_name, self.dev_path])
544
    if result.failed:
545
      raise errors.BlockDeviceError("command: %s error: %s" %
546
                                    (result.cmd, result.fail_reason))
547

    
548
    return snap_name
549

    
550
  def SetInfo(self, text):
551
    """Update metadata with info text.
552

553
    """
554
    BlockDev.SetInfo(self, text)
555

    
556
    # Replace invalid characters
557
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
558
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
559

    
560
    # Only up to 128 characters are allowed
561
    text = text[:128]
562

    
563
    result = utils.RunCmd(["lvchange", "--addtag", text,
564
                           self.dev_path])
565
    if result.failed:
566
      raise errors.BlockDeviceError("Command: %s error: %s" %
567
                                    (result.cmd, result.fail_reason))
568

    
569

    
570
class MDRaid1(BlockDev):
571
  """raid1 device implemented via md.
572

573
  """
574
  def __init__(self, unique_id, children):
575
    super(MDRaid1, self).__init__(unique_id, children)
576
    self.major = 9
577
    self.Attach()
578

    
579
  def Attach(self):
580
    """Find an array which matches our config and attach to it.
581

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

584
    """
585
    minor = self._FindMDByUUID(self.unique_id)
586
    if minor is not None:
587
      self._SetFromMinor(minor)
588
    else:
589
      self.minor = None
590
      self.dev_path = None
591

    
592
    return (minor is not None)
593

    
594
  @staticmethod
595
  def _GetUsedDevs():
596
    """Compute the list of in-use MD devices.
597

598
    It doesn't matter if the used device have other raid level, just
599
    that they are in use.
600

601
    """
602
    mdstat = open("/proc/mdstat", "r")
603
    data = mdstat.readlines()
604
    mdstat.close()
605

    
606
    used_md = {}
607
    valid_line = re.compile("^md([0-9]+) : .*$")
608
    for line in data:
609
      match = valid_line.match(line)
610
      if match:
611
        md_no = int(match.group(1))
612
        used_md[md_no] = line
613

    
614
    return used_md
615

    
616
  @staticmethod
617
  def _GetDevInfo(minor):
618
    """Get info about a MD device.
619

620
    Currently only uuid is returned.
621

622
    """
623
    result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
624
    if result.failed:
625
      logger.Error("Can't display md: %s" % result.fail_reason)
626
      return None
627
    retval = {}
628
    for line in result.stdout.splitlines():
629
      line = line.strip()
630
      kv = line.split(" : ", 1)
631
      if kv:
632
        if kv[0] == "UUID":
633
          retval["uuid"] = kv[1].split()[0]
634
        elif kv[0] == "State":
635
          retval["state"] = kv[1].split(", ")
636
    return retval
637

    
638
  @staticmethod
639
  def _FindUnusedMinor():
640
    """Compute an unused MD minor.
641

642
    This code assumes that there are 256 minors only.
643

644
    """
645
    used_md = MDRaid1._GetUsedDevs()
646
    i = 0
647
    while i < 256:
648
      if i not in used_md:
649
        break
650
      i += 1
651
    if i == 256:
652
      logger.Error("Critical: Out of md minor numbers.")
653
      raise errors.BlockDeviceError("Can't find a free MD minor")
654
    return i
655

    
656
  @classmethod
657
  def _FindMDByUUID(cls, uuid):
658
    """Find the minor of an MD array with a given UUID.
659

660
    """
661
    md_list = cls._GetUsedDevs()
662
    for minor in md_list:
663
      info = cls._GetDevInfo(minor)
664
      if info and info["uuid"] == uuid:
665
        return minor
666
    return None
667

    
668
  @staticmethod
669
  def _ZeroSuperblock(dev_path):
670
    """Zero the possible locations for an MD superblock.
671

672
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
673
    fails in versions 2.x with the same error code as non-writable
674
    device.
675

676
    The superblocks are located at (negative values are relative to
677
    the end of the block device):
678
      - -128k to end for version 0.90 superblock
679
      - -8k to -12k for version 1.0 superblock (included in the above)
680
      - 0k to 4k for version 1.1 superblock
681
      - 4k to 8k for version 1.2 superblock
682

683
    To cover all situations, the zero-ing will be:
684
      - 0k to 128k
685
      - -128k to end
686

687
    As such, the minimum device size must be 128k, otherwise we'll get
688
    I/O errors.
689

690
    Note that this function depends on the fact that one can open,
691
    read and write block devices normally.
692

693
    """
694
    overwrite_size = 128 * 1024
695
    empty_buf = '\0' * overwrite_size
696
    fd = open(dev_path, "r+")
697
    try:
698
      fd.seek(0, 0)
699
      p1 = fd.tell()
700
      fd.write(empty_buf)
701
      p2 = fd.tell()
702
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
703
      fd.seek(-overwrite_size, 2)
704
      p1 = fd.tell()
705
      fd.write(empty_buf)
706
      p2 = fd.tell()
707
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
708
    finally:
709
      fd.close()
710

    
711
  @classmethod
712
  def Create(cls, unique_id, children, size):
713
    """Create a new MD raid1 array.
714

715
    """
716
    if not isinstance(children, (tuple, list)):
717
      raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
718
                       str(children))
719
    for i in children:
720
      if not isinstance(i, BlockDev):
721
        raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
722
    for i in children:
723
      try:
724
        cls._ZeroSuperblock(i.dev_path)
725
      except EnvironmentError, err:
726
        logger.Error("Can't zero superblock for %s: %s" %
727
                     (i.dev_path, str(err)))
728
        return None
729
    minor = cls._FindUnusedMinor()
730
    result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
731
                           "--auto=yes", "--force", "-l1",
732
                           "-n%d" % len(children)] +
733
                          [dev.dev_path for dev in children])
734

    
735
    if result.failed:
736
      logger.Error("Can't create md: %s: %s" % (result.fail_reason,
737
                                                result.output))
738
      return None
739
    info = cls._GetDevInfo(minor)
740
    if not info or not "uuid" in info:
741
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
742
      return None
743
    return MDRaid1(info["uuid"], children)
744

    
745
  def Remove(self):
746
    """Stub remove function for MD RAID 1 arrays.
747

748
    We don't remove the superblock right now. Mark a to do.
749

750
    """
751
    #TODO: maybe zero superblock on child devices?
752
    return self.Shutdown()
753

    
754
  def Rename(self, new_id):
755
    """Rename a device.
756

757
    This is not supported for md raid1 devices.
758

759
    """
760
    raise errors.ProgrammerError("Can't rename a md raid1 device")
761

    
762
  def AddChildren(self, devices):
763
    """Add new member(s) to the md raid1.
764

765
    """
766
    if self.minor is None and not self.Attach():
767
      raise errors.BlockDeviceError("Can't attach to device")
768

    
769
    args = ["mdadm", "-a", self.dev_path]
770
    for dev in devices:
771
      if dev.dev_path is None:
772
        raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
773
      dev.Open()
774
      args.append(dev.dev_path)
775
    result = utils.RunCmd(args)
776
    if result.failed:
777
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
778
                                    result.output)
779
    new_len = len(self._children) + len(devices)
780
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
781
    if result.failed:
782
      raise errors.BlockDeviceError("Can't grow md array: %s" %
783
                                    result.output)
784
    self._children.extend(devices)
785

    
786
  def RemoveChildren(self, devices):
787
    """Remove member(s) from the md raid1.
788

789
    """
790
    if self.minor is None and not self.Attach():
791
      raise errors.BlockDeviceError("Can't attach to device")
792
    new_len = len(self._children) - len(devices)
793
    if new_len < 1:
794
      raise errors.BlockDeviceError("Can't reduce to less than one child")
795
    args = ["mdadm", "-f", self.dev_path]
796
    orig_devs = []
797
    for dev in devices:
798
      args.append(dev)
799
      for c in self._children:
800
        if c.dev_path == dev:
801
          orig_devs.append(c)
802
          break
803
      else:
804
        raise errors.BlockDeviceError("Can't find device '%s' for removal" %
805
                                      dev)
806
    result = utils.RunCmd(args)
807
    if result.failed:
808
      raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
809
                                    result.output)
810

    
811
    # it seems here we need a short delay for MD to update its
812
    # superblocks
813
    time.sleep(0.5)
814
    args[1] = "-r"
815
    result = utils.RunCmd(args)
816
    if result.failed:
817
      raise errors.BlockDeviceError("Failed to remove device(s) from array:"
818
                                    " %s" % result.output)
819
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
820
                           "-n", new_len])
821
    if result.failed:
822
      raise errors.BlockDeviceError("Can't shrink md array: %s" %
823
                                    result.output)
824
    for dev in orig_devs:
825
      self._children.remove(dev)
826

    
827
  def GetStatus(self):
828
    """Return the status of the device.
829

830
    """
831
    self.Attach()
832
    if self.minor is None:
833
      retval = self.STATUS_UNKNOWN
834
    else:
835
      retval = self.STATUS_ONLINE
836
    return retval
837

    
838
  def _SetFromMinor(self, minor):
839
    """Set our parameters based on the given minor.
840

841
    This sets our minor variable and our dev_path.
842

843
    """
844
    self.minor = minor
845
    self.dev_path = "/dev/md%d" % minor
846

    
847
  def Assemble(self):
848
    """Assemble the MD device.
849

850
    At this point we should have:
851
      - list of children devices
852
      - uuid
853

854
    """
855
    result = super(MDRaid1, self).Assemble()
856
    if not result:
857
      return result
858
    md_list = self._GetUsedDevs()
859
    for minor in md_list:
860
      info = self._GetDevInfo(minor)
861
      if info and info["uuid"] == self.unique_id:
862
        self._SetFromMinor(minor)
863
        logger.Info("MD array %s already started" % str(self))
864
        return True
865
    free_minor = self._FindUnusedMinor()
866
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
867
                           self.unique_id, "/dev/md%d" % free_minor] +
868
                          [bdev.dev_path for bdev in self._children])
869
    if result.failed:
870
      logger.Error("Can't assemble MD array: %s: %s" %
871
                   (result.fail_reason, result.output))
872
      self.minor = None
873
    else:
874
      self.minor = free_minor
875
    return not result.failed
876

    
877
  def Shutdown(self):
878
    """Tear down the MD array.
879

880
    This does a 'mdadm --stop' so after this command, the array is no
881
    longer available.
882

883
    """
884
    if self.minor is None and not self.Attach():
885
      logger.Info("MD object not attached to a device")
886
      return True
887

    
888
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
889
    if result.failed:
890
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
891
      return False
892
    self.minor = None
893
    self.dev_path = None
894
    return True
895

    
896
  def SetSyncSpeed(self, kbytes):
897
    """Set the maximum sync speed for the MD array.
898

899
    """
900
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
901
    if self.minor is None:
902
      logger.Error("MD array not attached to a device")
903
      return False
904
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
905
    try:
906
      f.write("%d" % kbytes)
907
    finally:
908
      f.close()
909
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
910
    try:
911
      f.write("%d" % (kbytes/2))
912
    finally:
913
      f.close()
914
    return result
915

    
916
  def GetSyncStatus(self):
917
    """Returns the sync status of the device.
918

919
    Returns:
920
     (sync_percent, estimated_time, is_degraded, ldisk)
921

922
    If sync_percent is None, it means all is ok
923
    If estimated_time is None, it means we can't esimate
924
    the time needed, otherwise it's the time left in seconds.
925

926
    The ldisk parameter is always true for MD devices.
927

928
    """
929
    if self.minor is None and not self.Attach():
930
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
931
    dev_info = self._GetDevInfo(self.minor)
932
    is_clean = ("state" in dev_info and
933
                len(dev_info["state"]) == 1 and
934
                dev_info["state"][0] in ("clean", "active"))
935
    sys_path = "/sys/block/md%s/md/" % self.minor
936
    f = file(sys_path + "sync_action")
937
    sync_status = f.readline().strip()
938
    f.close()
939
    if sync_status == "idle":
940
      return None, None, not is_clean, False
941
    f = file(sys_path + "sync_completed")
942
    sync_completed = f.readline().strip().split(" / ")
943
    f.close()
944
    if len(sync_completed) != 2:
945
      return 0, None, not is_clean, False
946
    sync_done, sync_total = [float(i) for i in sync_completed]
947
    sync_percent = 100.0*sync_done/sync_total
948
    f = file(sys_path + "sync_speed")
949
    sync_speed_k = int(f.readline().strip())
950
    if sync_speed_k == 0:
951
      time_est = None
952
    else:
953
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
954
    return sync_percent, time_est, not is_clean, False
955

    
956
  def Open(self, force=False):
957
    """Make the device ready for I/O.
958

959
    This is a no-op for the MDRaid1 device type, although we could use
960
    the 2.6.18's new array_state thing.
961

962
    """
963
    pass
964

    
965
  def Close(self):
966
    """Notifies that the device will no longer be used for I/O.
967

968
    This is a no-op for the MDRaid1 device type, but see comment for
969
    `Open()`.
970

971
    """
972
    pass
973

    
974

    
975
class BaseDRBD(BlockDev):
976
  """Base DRBD class.
977

978
  This class contains a few bits of common functionality between the
979
  0.7 and 8.x versions of DRBD.
980

981
  """
982
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
983
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
984

    
985
  _DRBD_MAJOR = 147
986
  _ST_UNCONFIGURED = "Unconfigured"
987
  _ST_WFCONNECTION = "WFConnection"
988
  _ST_CONNECTED = "Connected"
989

    
990
  @staticmethod
991
  def _GetProcData():
992
    """Return data from /proc/drbd.
993

994
    """
995
    stat = open("/proc/drbd", "r")
996
    try:
997
      data = stat.read().splitlines()
998
    finally:
999
      stat.close()
1000
    if not data:
1001
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
1002
    return data
1003

    
1004
  @staticmethod
1005
  def _MassageProcData(data):
1006
    """Transform the output of _GetProdData into a nicer form.
1007

1008
    Returns:
1009
      a dictionary of minor: joined lines from /proc/drbd for that minor
1010

1011
    """
1012
    lmatch = re.compile("^ *([0-9]+):.*$")
1013
    results = {}
1014
    old_minor = old_line = None
1015
    for line in data:
1016
      lresult = lmatch.match(line)
1017
      if lresult is not None:
1018
        if old_minor is not None:
1019
          results[old_minor] = old_line
1020
        old_minor = int(lresult.group(1))
1021
        old_line = line
1022
      else:
1023
        if old_minor is not None:
1024
          old_line += " " + line.strip()
1025
    # add last line
1026
    if old_minor is not None:
1027
      results[old_minor] = old_line
1028
    return results
1029

    
1030
  @classmethod
1031
  def _GetVersion(cls):
1032
    """Return the DRBD version.
1033

1034
    This will return a dict with keys:
1035
      k_major,
1036
      k_minor,
1037
      k_point,
1038
      api,
1039
      proto,
1040
      proto2 (only on drbd > 8.2.X)
1041

1042
    """
1043
    proc_data = cls._GetProcData()
1044
    first_line = proc_data[0].strip()
1045
    version = cls._VERSION_RE.match(first_line)
1046
    if not version:
1047
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1048
                                    first_line)
1049

    
1050
    values = version.groups()
1051
    retval = {'k_major': int(values[0]),
1052
              'k_minor': int(values[1]),
1053
              'k_point': int(values[2]),
1054
              'api': int(values[3]),
1055
              'proto': int(values[4]),
1056
             }
1057
    if values[5] is not None:
1058
      retval['proto2'] = values[5]
1059

    
1060
    return retval
1061

    
1062
  @staticmethod
1063
  def _DevPath(minor):
1064
    """Return the path to a drbd device for a given minor.
1065

1066
    """
1067
    return "/dev/drbd%d" % minor
1068

    
1069
  @classmethod
1070
  def _GetUsedDevs(cls):
1071
    """Compute the list of used DRBD devices.
1072

1073
    """
1074
    data = cls._GetProcData()
1075

    
1076
    used_devs = {}
1077
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1078
    for line in data:
1079
      match = valid_line.match(line)
1080
      if not match:
1081
        continue
1082
      minor = int(match.group(1))
1083
      state = match.group(2)
1084
      if state == cls._ST_UNCONFIGURED:
1085
        continue
1086
      used_devs[minor] = state, line
1087

    
1088
    return used_devs
1089

    
1090
  def _SetFromMinor(self, minor):
1091
    """Set our parameters based on the given minor.
1092

1093
    This sets our minor variable and our dev_path.
1094

1095
    """
1096
    if minor is None:
1097
      self.minor = self.dev_path = None
1098
    else:
1099
      self.minor = minor
1100
      self.dev_path = self._DevPath(minor)
1101

    
1102
  @staticmethod
1103
  def _CheckMetaSize(meta_device):
1104
    """Check if the given meta device looks like a valid one.
1105

1106
    This currently only check the size, which must be around
1107
    128MiB.
1108

1109
    """
1110
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1111
    if result.failed:
1112
      logger.Error("Failed to get device size: %s" % result.fail_reason)
1113
      return False
1114
    try:
1115
      sectors = int(result.stdout)
1116
    except ValueError:
1117
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1118
      return False
1119
    bytes = sectors * 512
1120
    if bytes < 128 * 1024 * 1024: # less than 128MiB
1121
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
1122
      return False
1123
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
1124
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
1125
      return False
1126
    return True
1127

    
1128
  def Rename(self, new_id):
1129
    """Rename a device.
1130

1131
    This is not supported for drbd devices.
1132

1133
    """
1134
    raise errors.ProgrammerError("Can't rename a drbd device")
1135

    
1136

    
1137
class DRBDev(BaseDRBD):
1138
  """DRBD block device.
1139

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

1144
  The unique_id for the drbd device is the (local_ip, local_port,
1145
  remote_ip, remote_port) tuple, and it must have two children: the
1146
  data device and the meta_device. The meta device is checked for
1147
  valid size and is zeroed on create.
1148

1149
  """
1150
  def __init__(self, unique_id, children):
1151
    super(DRBDev, self).__init__(unique_id, children)
1152
    self.major = self._DRBD_MAJOR
1153
    version = self._GetVersion()
1154
    if version['k_major'] != 0 and version['k_minor'] != 7:
1155
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1156
                                    " requested ganeti usage: kernel is"
1157
                                    " %s.%s, ganeti wants 0.7" %
1158
                                    (version['k_major'], version['k_minor']))
1159
    if len(children) != 2:
1160
      raise ValueError("Invalid configuration data %s" % str(children))
1161
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1162
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1163
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1164
    self.Attach()
1165

    
1166
  @classmethod
1167
  def _FindUnusedMinor(cls):
1168
    """Find an unused DRBD device.
1169

1170
    """
1171
    data = cls._GetProcData()
1172

    
1173
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1174
    for line in data:
1175
      match = valid_line.match(line)
1176
      if match:
1177
        return int(match.group(1))
1178
    logger.Error("Error: no free drbd minors!")
1179
    raise errors.BlockDeviceError("Can't find a free DRBD minor")
1180

    
1181
  @classmethod
1182
  def _GetDevInfo(cls, minor):
1183
    """Get details about a given DRBD minor.
1184

1185
    This return, if available, the local backing device in (major,
1186
    minor) formant and the local and remote (ip, port) information.
1187

1188
    """
1189
    data = {}
1190
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1191
    if result.failed:
1192
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1193
      return data
1194
    out = result.stdout
1195
    if out == "Not configured\n":
1196
      return data
1197
    for line in out.splitlines():
1198
      if "local_dev" not in data:
1199
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1200
        if match:
1201
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1202
          continue
1203
      if "meta_dev" not in data:
1204
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1205
        if match:
1206
          if match.group(2) is not None and match.group(3) is not None:
1207
            # matched on the major/minor
1208
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1209
          else:
1210
            # matched on the "internal" string
1211
            data["meta_dev"] = match.group(1)
1212
            # in this case, no meta_index is in the output
1213
            data["meta_index"] = -1
1214
          continue
1215
      if "meta_index" not in data:
1216
        match = re.match("^Meta index: ([0-9]+).*$", line)
1217
        if match:
1218
          data["meta_index"] = int(match.group(1))
1219
          continue
1220
      if "local_addr" not in data:
1221
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1222
        if match:
1223
          data["local_addr"] = (match.group(1), int(match.group(2)))
1224
          continue
1225
      if "remote_addr" not in data:
1226
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1227
        if match:
1228
          data["remote_addr"] = (match.group(1), int(match.group(2)))
1229
          continue
1230
    return data
1231

    
1232
  def _MatchesLocal(self, info):
1233
    """Test if our local config matches with an existing device.
1234

1235
    The parameter should be as returned from `_GetDevInfo()`. This
1236
    method tests if our local backing device is the same as the one in
1237
    the info parameter, in effect testing if we look like the given
1238
    device.
1239

1240
    """
1241
    if not ("local_dev" in info and "meta_dev" in info and
1242
            "meta_index" in info):
1243
      return False
1244

    
1245
    backend = self._children[0]
1246
    if backend is not None:
1247
      retval = (info["local_dev"] == (backend.major, backend.minor))
1248
    else:
1249
      retval = (info["local_dev"] == (0, 0))
1250
    meta = self._children[1]
1251
    if meta is not None:
1252
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1253
      retval = retval and (info["meta_index"] == 0)
1254
    else:
1255
      retval = retval and (info["meta_dev"] == "internal" and
1256
                           info["meta_index"] == -1)
1257
    return retval
1258

    
1259
  def _MatchesNet(self, info):
1260
    """Test if our network config matches with an existing device.
1261

1262
    The parameter should be as returned from `_GetDevInfo()`. This
1263
    method tests if our network configuration is the same as the one
1264
    in the info parameter, in effect testing if we look like the given
1265
    device.
1266

1267
    """
1268
    if (((self._lhost is None and not ("local_addr" in info)) and
1269
         (self._rhost is None and not ("remote_addr" in info)))):
1270
      return True
1271

    
1272
    if self._lhost is None:
1273
      return False
1274

    
1275
    if not ("local_addr" in info and
1276
            "remote_addr" in info):
1277
      return False
1278

    
1279
    retval = (info["local_addr"] == (self._lhost, self._lport))
1280
    retval = (retval and
1281
              info["remote_addr"] == (self._rhost, self._rport))
1282
    return retval
1283

    
1284
  @classmethod
1285
  def _AssembleLocal(cls, minor, backend, meta):
1286
    """Configure the local part of a DRBD device.
1287

1288
    This is the first thing that must be done on an unconfigured DRBD
1289
    device. And it must be done only once.
1290

1291
    """
1292
    if not cls._CheckMetaSize(meta):
1293
      return False
1294
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1295
                           backend, meta, "0", "-e", "detach"])
1296
    if result.failed:
1297
      logger.Error("Can't attach local disk: %s" % result.output)
1298
    return not result.failed
1299

    
1300
  @classmethod
1301
  def _ShutdownLocal(cls, minor):
1302
    """Detach from the local device.
1303

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

1307
    """
1308
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1309
    if result.failed:
1310
      logger.Error("Can't detach local device: %s" % result.output)
1311
    return not result.failed
1312

    
1313
  @staticmethod
1314
  def _ShutdownAll(minor):
1315
    """Deactivate the device.
1316

1317
    This will, of course, fail if the device is in use.
1318

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

    
1325
  @classmethod
1326
  def _AssembleNet(cls, minor, net_info, protocol):
1327
    """Configure the network part of the device.
1328

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

1336
    """
1337
    lhost, lport, rhost, rport = net_info
1338
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1339
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1340
                           protocol])
1341
    if result.failed:
1342
      logger.Error("Can't setup network for dbrd device: %s" %
1343
                   result.fail_reason)
1344
      return False
1345

    
1346
    timeout = time.time() + 10
1347
    ok = False
1348
    while time.time() < timeout:
1349
      info = cls._GetDevInfo(minor)
1350
      if not "local_addr" in info or not "remote_addr" in info:
1351
        time.sleep(1)
1352
        continue
1353
      if (info["local_addr"] != (lhost, lport) or
1354
          info["remote_addr"] != (rhost, rport)):
1355
        time.sleep(1)
1356
        continue
1357
      ok = True
1358
      break
1359
    if not ok:
1360
      logger.Error("Timeout while configuring network")
1361
      return False
1362
    return True
1363

    
1364
  @classmethod
1365
  def _ShutdownNet(cls, minor):
1366
    """Disconnect from the remote peer.
1367

1368
    This fails if we don't have a local device.
1369

1370
    """
1371
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1372
    if result.failed:
1373
      logger.Error("Can't shutdown network: %s" % result.output)
1374
    return not result.failed
1375

    
1376
  def Assemble(self):
1377
    """Assemble the drbd.
1378

1379
    Method:
1380
      - if we have a local backing device, we bind to it by:
1381
        - checking the list of used drbd devices
1382
        - check if the local minor use of any of them is our own device
1383
        - if yes, abort?
1384
        - if not, bind
1385
      - if we have a local/remote net info:
1386
        - redo the local backing device step for the remote device
1387
        - check if any drbd device is using the local port,
1388
          if yes abort
1389
        - check if any remote drbd device is using the remote
1390
          port, if yes abort (for now)
1391
        - bind our net port
1392
        - bind the remote net port
1393

1394
    """
1395
    self.Attach()
1396
    if self.minor is not None:
1397
      logger.Info("Already assembled")
1398
      return True
1399

    
1400
    result = super(DRBDev, self).Assemble()
1401
    if not result:
1402
      return result
1403

    
1404
    minor = self._FindUnusedMinor()
1405
    need_localdev_teardown = False
1406
    if self._children[0]:
1407
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1408
                                   self._children[1].dev_path)
1409
      if not result:
1410
        return False
1411
      need_localdev_teardown = True
1412
    if self._lhost and self._lport and self._rhost and self._rport:
1413
      result = self._AssembleNet(minor,
1414
                                 (self._lhost, self._lport,
1415
                                  self._rhost, self._rport),
1416
                                 "C")
1417
      if not result:
1418
        if need_localdev_teardown:
1419
          # we will ignore failures from this
1420
          logger.Error("net setup failed, tearing down local device")
1421
          self._ShutdownAll(minor)
1422
        return False
1423
    self._SetFromMinor(minor)
1424
    return True
1425

    
1426
  def Shutdown(self):
1427
    """Shutdown the DRBD device.
1428

1429
    """
1430
    if self.minor is None and not self.Attach():
1431
      logger.Info("DRBD device not attached to a device during Shutdown")
1432
      return True
1433
    if not self._ShutdownAll(self.minor):
1434
      return False
1435
    self.minor = None
1436
    self.dev_path = None
1437
    return True
1438

    
1439
  def Attach(self):
1440
    """Find a DRBD device which matches our config and attach to it.
1441

1442
    In case of partially attached (local device matches but no network
1443
    setup), we perform the network attach. If successful, we re-test
1444
    the attach if can return success.
1445

1446
    """
1447
    for minor in self._GetUsedDevs():
1448
      info = self._GetDevInfo(minor)
1449
      match_l = self._MatchesLocal(info)
1450
      match_r = self._MatchesNet(info)
1451
      if match_l and match_r:
1452
        break
1453
      if match_l and not match_r and "local_addr" not in info:
1454
        res_r = self._AssembleNet(minor,
1455
                                  (self._lhost, self._lport,
1456
                                   self._rhost, self._rport),
1457
                                  "C")
1458
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1459
          break
1460
    else:
1461
      minor = None
1462

    
1463
    self._SetFromMinor(minor)
1464
    return minor is not None
1465

    
1466
  def Open(self, force=False):
1467
    """Make the local state primary.
1468

1469
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1470
    is given. Since this is a pottentialy dangerous operation, the
1471
    force flag should be only given after creation, when it actually
1472
    has to be given.
1473

1474
    """
1475
    if self.minor is None and not self.Attach():
1476
      logger.Error("DRBD cannot attach to a device during open")
1477
      return False
1478
    cmd = ["drbdsetup", self.dev_path, "primary"]
1479
    if force:
1480
      cmd.append("--do-what-I-say")
1481
    result = utils.RunCmd(cmd)
1482
    if result.failed:
1483
      msg = ("Can't make drbd device primary: %s" % result.output)
1484
      logger.Error(msg)
1485
      raise errors.BlockDeviceError(msg)
1486

    
1487
  def Close(self):
1488
    """Make the local state secondary.
1489

1490
    This will, of course, fail if the device is in use.
1491

1492
    """
1493
    if self.minor is None and not self.Attach():
1494
      logger.Info("Instance not attached to a device")
1495
      raise errors.BlockDeviceError("Can't find device")
1496
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1497
    if result.failed:
1498
      msg = ("Can't switch drbd device to"
1499
             " secondary: %s" % result.output)
1500
      logger.Error(msg)
1501
      raise errors.BlockDeviceError(msg)
1502

    
1503
  def SetSyncSpeed(self, kbytes):
1504
    """Set the speed of the DRBD syncer.
1505

1506
    """
1507
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1508
    if self.minor is None:
1509
      logger.Info("Instance not attached to a device")
1510
      return False
1511
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1512
                           kbytes])
1513
    if result.failed:
1514
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1515
    return not result.failed and children_result
1516

    
1517
  def GetSyncStatus(self):
1518
    """Returns the sync status of the device.
1519

1520
    Returns:
1521
     (sync_percent, estimated_time, is_degraded, ldisk)
1522

1523
    If sync_percent is None, it means all is ok
1524
    If estimated_time is None, it means we can't esimate
1525
    the time needed, otherwise it's the time left in seconds.
1526

1527
    The ldisk parameter will be returned as True, since the DRBD7
1528
    devices have not been converted.
1529

1530
    """
1531
    if self.minor is None and not self.Attach():
1532
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1533
    proc_info = self._MassageProcData(self._GetProcData())
1534
    if self.minor not in proc_info:
1535
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1536
                                    self.minor)
1537
    line = proc_info[self.minor]
1538
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1539
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1540
    if match:
1541
      sync_percent = float(match.group(1))
1542
      hours = int(match.group(2))
1543
      minutes = int(match.group(3))
1544
      seconds = int(match.group(4))
1545
      est_time = hours * 3600 + minutes * 60 + seconds
1546
    else:
1547
      sync_percent = None
1548
      est_time = None
1549
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1550
    if not match:
1551
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1552
                                    self.minor)
1553
    client_state = match.group(1)
1554
    is_degraded = client_state != "Connected"
1555
    return sync_percent, est_time, is_degraded, False
1556

    
1557
  def GetStatus(self):
1558
    """Compute the status of the DRBD device
1559

1560
    Note that DRBD devices don't have the STATUS_EXISTING state.
1561

1562
    """
1563
    if self.minor is None and not self.Attach():
1564
      return self.STATUS_UNKNOWN
1565

    
1566
    data = self._GetProcData()
1567
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1568
                       self.minor)
1569
    for line in data:
1570
      mresult = match.match(line)
1571
      if mresult:
1572
        break
1573
    else:
1574
      logger.Error("Can't find myself!")
1575
      return self.STATUS_UNKNOWN
1576

    
1577
    state = mresult.group(2)
1578
    if state == "Primary":
1579
      result = self.STATUS_ONLINE
1580
    else:
1581
      result = self.STATUS_STANDBY
1582

    
1583
    return result
1584

    
1585
  @staticmethod
1586
  def _ZeroDevice(device):
1587
    """Zero a device.
1588

1589
    This writes until we get ENOSPC.
1590

1591
    """
1592
    f = open(device, "w")
1593
    buf = "\0" * 1048576
1594
    try:
1595
      while True:
1596
        f.write(buf)
1597
    except IOError, err:
1598
      if err.errno != errno.ENOSPC:
1599
        raise
1600

    
1601
  @classmethod
1602
  def Create(cls, unique_id, children, size):
1603
    """Create a new DRBD device.
1604

1605
    Since DRBD devices are not created per se, just assembled, this
1606
    function just zeroes the meta device.
1607

1608
    """
1609
    if len(children) != 2:
1610
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1611
    meta = children[1]
1612
    meta.Assemble()
1613
    if not meta.Attach():
1614
      raise errors.BlockDeviceError("Can't attach to meta device")
1615
    if not cls._CheckMetaSize(meta.dev_path):
1616
      raise errors.BlockDeviceError("Invalid meta device")
1617
    logger.Info("Started zeroing device %s" % meta.dev_path)
1618
    cls._ZeroDevice(meta.dev_path)
1619
    logger.Info("Done zeroing device %s" % meta.dev_path)
1620
    return cls(unique_id, children)
1621

    
1622
  def Remove(self):
1623
    """Stub remove for DRBD devices.
1624

1625
    """
1626
    return self.Shutdown()
1627

    
1628

    
1629
class DRBD8(BaseDRBD):
1630
  """DRBD v8.x block device.
1631

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

1636
  The unique_id for the drbd device is the (local_ip, local_port,
1637
  remote_ip, remote_port) tuple, and it must have two children: the
1638
  data device and the meta_device. The meta device is checked for
1639
  valid size and is zeroed on create.
1640

1641
  """
1642
  _MAX_MINORS = 255
1643
  _PARSE_SHOW = None
1644

    
1645
  def __init__(self, unique_id, children):
1646
    if children and children.count(None) > 0:
1647
      children = []
1648
    super(DRBD8, self).__init__(unique_id, children)
1649
    self.major = self._DRBD_MAJOR
1650
    version = self._GetVersion()
1651
    if version['k_major'] != 8 :
1652
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1653
                                    " requested ganeti usage: kernel is"
1654
                                    " %s.%s, ganeti wants 8.x" %
1655
                                    (version['k_major'], version['k_minor']))
1656

    
1657
    if len(children) not in (0, 2):
1658
      raise ValueError("Invalid configuration data %s" % str(children))
1659
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1660
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1661
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1662
    self.Attach()
1663

    
1664
  @classmethod
1665
  def _InitMeta(cls, minor, dev_path):
1666
    """Initialize a meta device.
1667

1668
    This will not work if the given minor is in use.
1669

1670
    """
1671
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1672
                           "v08", dev_path, "0", "create-md"])
1673
    if result.failed:
1674
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1675
                                    result.output)
1676

    
1677
  @classmethod
1678
  def _FindUnusedMinor(cls):
1679
    """Find an unused DRBD device.
1680

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

1684
    """
1685
    data = cls._GetProcData()
1686

    
1687
    unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1688
    used_line = re.compile("^ *([0-9]+): cs:")
1689
    highest = None
1690
    for line in data:
1691
      match = unused_line.match(line)
1692
      if match:
1693
        return int(match.group(1))
1694
      match = used_line.match(line)
1695
      if match:
1696
        minor = int(match.group(1))
1697
        highest = max(highest, minor)
1698
    if highest is None: # there are no minors in use at all
1699
      return 0
1700
    if highest >= cls._MAX_MINORS:
1701
      logger.Error("Error: no free drbd minors!")
1702
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1703
    return highest + 1
1704

    
1705
  @classmethod
1706
  def _IsValidMeta(cls, meta_device):
1707
    """Check if the given meta device looks like a valid one.
1708

1709
    """
1710
    minor = cls._FindUnusedMinor()
1711
    minor_path = cls._DevPath(minor)
1712
    result = utils.RunCmd(["drbdmeta", minor_path,
1713
                           "v08", meta_device, "0",
1714
                           "dstate"])
1715
    if result.failed:
1716
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1717
      return False
1718
    return True
1719

    
1720
  @classmethod
1721
  def _GetShowParser(cls):
1722
    """Return a parser for `drbd show` output.
1723

1724
    This will either create or return an already-create parser for the
1725
    output of the command `drbd show`.
1726

1727
    """
1728
    if cls._PARSE_SHOW is not None:
1729
      return cls._PARSE_SHOW
1730

    
1731
    # pyparsing setup
1732
    lbrace = pyp.Literal("{").suppress()
1733
    rbrace = pyp.Literal("}").suppress()
1734
    semi = pyp.Literal(";").suppress()
1735
    # this also converts the value to an int
1736
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1737

    
1738
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1739
    defa = pyp.Literal("_is_default").suppress()
1740
    dbl_quote = pyp.Literal('"').suppress()
1741

    
1742
    keyword = pyp.Word(pyp.alphanums + '-')
1743

    
1744
    # value types
1745
    value = pyp.Word(pyp.alphanums + '_-/.:')
1746
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1747
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1748
                 number)
1749
    # meta device, extended syntax
1750
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1751
                  number + pyp.Word(']').suppress())
1752

    
1753
    # a statement
1754
    stmt = (~rbrace + keyword + ~lbrace +
1755
            (addr_port ^ value ^ quoted ^ meta_value) +
1756
            pyp.Optional(defa) + semi +
1757
            pyp.Optional(pyp.restOfLine).suppress())
1758

    
1759
    # an entire section
1760
    section_name = pyp.Word(pyp.alphas + '_')
1761
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1762

    
1763
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1764
    bnf.ignore(comment)
1765

    
1766
    cls._PARSE_SHOW = bnf
1767

    
1768
    return bnf
1769

    
1770
  @classmethod
1771
  def _GetShowData(cls, minor):
1772
    """Return the `drbdsetup show` data for a minor.
1773

1774
    """
1775
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1776
    if result.failed:
1777
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1778
      return None
1779
    return result.stdout
1780

    
1781
  @classmethod
1782
  def _GetDevInfo(cls, out):
1783
    """Parse details about a given DRBD minor.
1784

1785
    This return, if available, the local backing device (as a path)
1786
    and the local and remote (ip, port) information from a string
1787
    containing the output of the `drbdsetup show` command as returned
1788
    by _GetShowData.
1789

1790
    """
1791
    data = {}
1792
    if not out:
1793
      return data
1794

    
1795
    bnf = cls._GetShowParser()
1796
    # run pyparse
1797

    
1798
    try:
1799
      results = bnf.parseString(out)
1800
    except pyp.ParseException, err:
1801
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1802
                                    str(err))
1803

    
1804
    # and massage the results into our desired format
1805
    for section in results:
1806
      sname = section[0]
1807
      if sname == "_this_host":
1808
        for lst in section[1:]:
1809
          if lst[0] == "disk":
1810
            data["local_dev"] = lst[1]
1811
          elif lst[0] == "meta-disk":
1812
            data["meta_dev"] = lst[1]
1813
            data["meta_index"] = lst[2]
1814
          elif lst[0] == "address":
1815
            data["local_addr"] = tuple(lst[1:])
1816
      elif sname == "_remote_host":
1817
        for lst in section[1:]:
1818
          if lst[0] == "address":
1819
            data["remote_addr"] = tuple(lst[1:])
1820
    return data
1821

    
1822
  def _MatchesLocal(self, info):
1823
    """Test if our local config matches with an existing device.
1824

1825
    The parameter should be as returned from `_GetDevInfo()`. This
1826
    method tests if our local backing device is the same as the one in
1827
    the info parameter, in effect testing if we look like the given
1828
    device.
1829

1830
    """
1831
    if self._children:
1832
      backend, meta = self._children
1833
    else:
1834
      backend = meta = None
1835

    
1836
    if backend is not None:
1837
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1838
    else:
1839
      retval = ("local_dev" not in info)
1840

    
1841
    if meta is not None:
1842
      retval = retval and ("meta_dev" in info and
1843
                           info["meta_dev"] == meta.dev_path)
1844
      retval = retval and ("meta_index" in info and
1845
                           info["meta_index"] == 0)
1846
    else:
1847
      retval = retval and ("meta_dev" not in info and
1848
                           "meta_index" not in info)
1849
    return retval
1850

    
1851
  def _MatchesNet(self, info):
1852
    """Test if our network config matches with an existing device.
1853

1854
    The parameter should be as returned from `_GetDevInfo()`. This
1855
    method tests if our network configuration is the same as the one
1856
    in the info parameter, in effect testing if we look like the given
1857
    device.
1858

1859
    """
1860
    if (((self._lhost is None and not ("local_addr" in info)) and
1861
         (self._rhost is None and not ("remote_addr" in info)))):
1862
      return True
1863

    
1864
    if self._lhost is None:
1865
      return False
1866

    
1867
    if not ("local_addr" in info and
1868
            "remote_addr" in info):
1869
      return False
1870

    
1871
    retval = (info["local_addr"] == (self._lhost, self._lport))
1872
    retval = (retval and
1873
              info["remote_addr"] == (self._rhost, self._rport))
1874
    return retval
1875

    
1876
  @classmethod
1877
  def _AssembleLocal(cls, minor, backend, meta):
1878
    """Configure the local part of a DRBD device.
1879

1880
    This is the first thing that must be done on an unconfigured DRBD
1881
    device. And it must be done only once.
1882

1883
    """
1884
    if not cls._IsValidMeta(meta):
1885
      return False
1886
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1887
                           backend, meta, "0", "-e", "detach",
1888
                           "--create-device"])
1889
    if result.failed:
1890
      logger.Error("Can't attach local disk: %s" % result.output)
1891
    return not result.failed
1892

    
1893
  @classmethod
1894
  def _AssembleNet(cls, minor, net_info, protocol,
1895
                   dual_pri=False, hmac=None, secret=None):
1896
    """Configure the network part of the device.
1897

1898
    """
1899
    lhost, lport, rhost, rport = net_info
1900
    if None in net_info:
1901
      # we don't want network connection and actually want to make
1902
      # sure its shutdown
1903
      return cls._ShutdownNet(minor)
1904

    
1905
    args = ["drbdsetup", cls._DevPath(minor), "net",
1906
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1907
            "-A", "discard-zero-changes",
1908
            "-B", "consensus",
1909
            ]
1910
    if dual_pri:
1911
      args.append("-m")
1912
    if hmac and secret:
1913
      args.extend(["-a", hmac, "-x", secret])
1914
    result = utils.RunCmd(args)
1915
    if result.failed:
1916
      logger.Error("Can't setup network for dbrd device: %s" %
1917
                   result.fail_reason)
1918
      return False
1919

    
1920
    timeout = time.time() + 10
1921
    ok = False
1922
    while time.time() < timeout:
1923
      info = cls._GetDevInfo(cls._GetShowData(minor))
1924
      if not "local_addr" in info or not "remote_addr" in info:
1925
        time.sleep(1)
1926
        continue
1927
      if (info["local_addr"] != (lhost, lport) or
1928
          info["remote_addr"] != (rhost, rport)):
1929
        time.sleep(1)
1930
        continue
1931
      ok = True
1932
      break
1933
    if not ok:
1934
      logger.Error("Timeout while configuring network")
1935
      return False
1936
    return True
1937

    
1938
  def AddChildren(self, devices):
1939
    """Add a disk to the DRBD device.
1940

1941
    """
1942
    if self.minor is None:
1943
      raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1944
    if len(devices) != 2:
1945
      raise errors.BlockDeviceError("Need two devices for AddChildren")
1946
    info = self._GetDevInfo(self._GetShowData(self.minor))
1947
    if "local_dev" in info:
1948
      raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1949
    backend, meta = devices
1950
    if backend.dev_path is None or meta.dev_path is None:
1951
      raise errors.BlockDeviceError("Children not ready during AddChildren")
1952
    backend.Open()
1953
    meta.Open()
1954
    if not self._CheckMetaSize(meta.dev_path):
1955
      raise errors.BlockDeviceError("Invalid meta device size")
1956
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1957
    if not self._IsValidMeta(meta.dev_path):
1958
      raise errors.BlockDeviceError("Cannot initalize meta device")
1959

    
1960
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1961
      raise errors.BlockDeviceError("Can't attach to local storage")
1962
    self._children = devices
1963

    
1964
  def RemoveChildren(self, devices):
1965
    """Detach the drbd device from local storage.
1966

1967
    """
1968
    if self.minor is None:
1969
      raise errors.BlockDeviceError("Can't attach to drbd8 during"
1970
                                    " RemoveChildren")
1971
    # early return if we don't actually have backing storage
1972
    info = self._GetDevInfo(self._GetShowData(self.minor))
1973
    if "local_dev" not in info:
1974
      return
1975
    if len(self._children) != 2:
1976
      raise errors.BlockDeviceError("We don't have two children: %s" %
1977
                                    self._children)
1978
    if self._children.count(None) == 2: # we don't actually have children :)
1979
      logger.Error("Requested detach while detached")
1980
      return
1981
    if len(devices) != 2:
1982
      raise errors.BlockDeviceError("We need two children in RemoveChildren")
1983
    for child, dev in zip(self._children, devices):
1984
      if dev != child.dev_path:
1985
        raise errors.BlockDeviceError("Mismatch in local storage"
1986
                                      " (%s != %s) in RemoveChildren" %
1987
                                      (dev, child.dev_path))
1988

    
1989
    if not self._ShutdownLocal(self.minor):
1990
      raise errors.BlockDeviceError("Can't detach from local storage")
1991
    self._children = []
1992

    
1993
  def SetSyncSpeed(self, kbytes):
1994
    """Set the speed of the DRBD syncer.
1995

1996
    """
1997
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1998
    if self.minor is None:
1999
      logger.Info("Instance not attached to a device")
2000
      return False
2001
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
2002
                           kbytes])
2003
    if result.failed:
2004
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
2005
    return not result.failed and children_result
2006

    
2007
  def GetSyncStatus(self):
2008
    """Returns the sync status of the device.
2009

2010
    Returns:
2011
     (sync_percent, estimated_time, is_degraded)
2012

2013
    If sync_percent is None, it means all is ok
2014
    If estimated_time is None, it means we can't esimate
2015
    the time needed, otherwise it's the time left in seconds.
2016

2017

2018
    We set the is_degraded parameter to True on two conditions:
2019
    network not connected or local disk missing.
2020

2021
    We compute the ldisk parameter based on wheter we have a local
2022
    disk or not.
2023

2024
    """
2025
    if self.minor is None and not self.Attach():
2026
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
2027
    proc_info = self._MassageProcData(self._GetProcData())
2028
    if self.minor not in proc_info:
2029
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
2030
                                    self.minor)
2031
    line = proc_info[self.minor]
2032
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
2033
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
2034
    if match:
2035
      sync_percent = float(match.group(1))
2036
      hours = int(match.group(2))
2037
      minutes = int(match.group(3))
2038
      seconds = int(match.group(4))
2039
      est_time = hours * 3600 + minutes * 60 + seconds
2040
    else:
2041
      sync_percent = None
2042
      est_time = None
2043
    match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
2044
    if not match:
2045
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
2046
                                    self.minor)
2047
    client_state = match.group(1)
2048
    local_disk_state = match.group(2)
2049
    ldisk = local_disk_state != "UpToDate"
2050
    is_degraded = client_state != "Connected"
2051
    return sync_percent, est_time, is_degraded or ldisk, ldisk
2052

    
2053
  def GetStatus(self):
2054
    """Compute the status of the DRBD device
2055

2056
    Note that DRBD devices don't have the STATUS_EXISTING state.
2057

2058
    """
2059
    if self.minor is None and not self.Attach():
2060
      return self.STATUS_UNKNOWN
2061

    
2062
    data = self._GetProcData()
2063
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
2064
                       self.minor)
2065
    for line in data:
2066
      mresult = match.match(line)
2067
      if mresult:
2068
        break
2069
    else:
2070
      logger.Error("Can't find myself!")
2071
      return self.STATUS_UNKNOWN
2072

    
2073
    state = mresult.group(2)
2074
    if state == "Primary":
2075
      result = self.STATUS_ONLINE
2076
    else:
2077
      result = self.STATUS_STANDBY
2078

    
2079
    return result
2080

    
2081
  def Open(self, force=False):
2082
    """Make the local state primary.
2083

2084
    If the 'force' parameter is given, the '--do-what-I-say' parameter
2085
    is given. Since this is a pottentialy dangerous operation, the
2086
    force flag should be only given after creation, when it actually
2087
    has to be given.
2088

2089
    """
2090
    if self.minor is None and not self.Attach():
2091
      logger.Error("DRBD cannot attach to a device during open")
2092
      return False
2093
    cmd = ["drbdsetup", self.dev_path, "primary"]
2094
    if force:
2095
      cmd.append("-o")
2096
    result = utils.RunCmd(cmd)
2097
    if result.failed:
2098
      msg = ("Can't make drbd device primary: %s" % result.output)
2099
      logger.Error(msg)
2100
      raise errors.BlockDeviceError(msg)
2101

    
2102
  def Close(self):
2103
    """Make the local state secondary.
2104

2105
    This will, of course, fail if the device is in use.
2106

2107
    """
2108
    if self.minor is None and not self.Attach():
2109
      logger.Info("Instance not attached to a device")
2110
      raise errors.BlockDeviceError("Can't find device")
2111
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
2112
    if result.failed:
2113
      msg = ("Can't switch drbd device to"
2114
             " secondary: %s" % result.output)
2115
      logger.Error(msg)
2116
      raise errors.BlockDeviceError(msg)
2117

    
2118
  def Attach(self):
2119
    """Find a DRBD device which matches our config and attach to it.
2120

2121
    In case of partially attached (local device matches but no network
2122
    setup), we perform the network attach. If successful, we re-test
2123
    the attach if can return success.
2124

2125
    """
2126
    for minor in self._GetUsedDevs():
2127
      info = self._GetDevInfo(self._GetShowData(minor))
2128
      match_l = self._MatchesLocal(info)
2129
      match_r = self._MatchesNet(info)
2130
      if match_l and match_r:
2131
        break
2132
      if match_l and not match_r and "local_addr" not in info:
2133
        res_r = self._AssembleNet(minor,
2134
                                  (self._lhost, self._lport,
2135
                                   self._rhost, self._rport),
2136
                                  "C")
2137
        if res_r:
2138
          if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
2139
            break
2140
      # the weakest case: we find something that is only net attached
2141
      # even though we were passed some children at init time
2142
      if match_r and "local_dev" not in info:
2143
        break
2144
      if match_l and not match_r and "local_addr" in info:
2145
        # strange case - the device network part points to somewhere
2146
        # else, even though its local storage is ours; as we own the
2147
        # drbd space, we try to disconnect from the remote peer and
2148
        # reconnect to our correct one
2149
        if not self._ShutdownNet(minor):
2150
          raise errors.BlockDeviceError("Device has correct local storage,"
2151
                                        " wrong remote peer and is unable to"
2152
                                        " disconnect in order to attach to"
2153
                                        " the correct peer")
2154
        # note: _AssembleNet also handles the case when we don't want
2155
        # local storage (i.e. one or more of the _[lr](host|port) is
2156
        # None)
2157
        if (self._AssembleNet(minor, (self._lhost, self._lport,
2158
                                      self._rhost, self._rport), "C") and
2159
            self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
2160
          break
2161

    
2162
    else:
2163
      minor = None
2164

    
2165
    self._SetFromMinor(minor)
2166
    return minor is not None
2167

    
2168
  def Assemble(self):
2169
    """Assemble the drbd.
2170

2171
    Method:
2172
      - if we have a local backing device, we bind to it by:
2173
        - checking the list of used drbd devices
2174
        - check if the local minor use of any of them is our own device
2175
        - if yes, abort?
2176
        - if not, bind
2177
      - if we have a local/remote net info:
2178
        - redo the local backing device step for the remote device
2179
        - check if any drbd device is using the local port,
2180
          if yes abort
2181
        - check if any remote drbd device is using the remote
2182
          port, if yes abort (for now)
2183
        - bind our net port
2184
        - bind the remote net port
2185

2186
    """
2187
    self.Attach()
2188
    if self.minor is not None:
2189
      logger.Info("Already assembled")
2190
      return True
2191

    
2192
    result = super(DRBD8, self).Assemble()
2193
    if not result:
2194
      return result
2195

    
2196
    minor = self._FindUnusedMinor()
2197
    need_localdev_teardown = False
2198
    if self._children and self._children[0] and self._children[1]:
2199
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2200
                                   self._children[1].dev_path)
2201
      if not result:
2202
        return False
2203
      need_localdev_teardown = True
2204
    if self._lhost and self._lport and self._rhost and self._rport:
2205
      result = self._AssembleNet(minor,
2206
                                 (self._lhost, self._lport,
2207
                                  self._rhost, self._rport),
2208
                                 "C")
2209
      if not result:
2210
        if need_localdev_teardown:
2211
          # we will ignore failures from this
2212
          logger.Error("net setup failed, tearing down local device")
2213
          self._ShutdownAll(minor)
2214
        return False
2215
    self._SetFromMinor(minor)
2216
    return True
2217

    
2218
  @classmethod
2219
  def _ShutdownLocal(cls, minor):
2220
    """Detach from the local device.
2221

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

2225
    """
2226
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2227
    if result.failed:
2228
      logger.Error("Can't detach local device: %s" % result.output)
2229
    return not result.failed
2230

    
2231
  @classmethod
2232
  def _ShutdownNet(cls, minor):
2233
    """Disconnect from the remote peer.
2234

2235
    This fails if we don't have a local device.
2236

2237
    """
2238
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2239
    if result.failed:
2240
      logger.Error("Can't shutdown network: %s" % result.output)
2241
    return not result.failed
2242

    
2243
  @classmethod
2244
  def _ShutdownAll(cls, minor):
2245
    """Deactivate the device.
2246

2247
    This will, of course, fail if the device is in use.
2248

2249
    """
2250
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2251
    if result.failed:
2252
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2253
    return not result.failed
2254

    
2255
  def Shutdown(self):
2256
    """Shutdown the DRBD device.
2257

2258
    """
2259
    if self.minor is None and not self.Attach():
2260
      logger.Info("DRBD device not attached to a device during Shutdown")
2261
      return True
2262
    if not self._ShutdownAll(self.minor):
2263
      return False
2264
    self.minor = None
2265
    self.dev_path = None
2266
    return True
2267

    
2268
  def Remove(self):
2269
    """Stub remove for DRBD devices.
2270

2271
    """
2272
    return self.Shutdown()
2273

    
2274
  @classmethod
2275
  def Create(cls, unique_id, children, size):
2276
    """Create a new DRBD8 device.
2277

2278
    Since DRBD devices are not created per se, just assembled, this
2279
    function only initializes the metadata.
2280

2281
    """
2282
    if len(children) != 2:
2283
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2284
    meta = children[1]
2285
    meta.Assemble()
2286
    if not meta.Attach():
2287
      raise errors.BlockDeviceError("Can't attach to meta device")
2288
    if not cls._CheckMetaSize(meta.dev_path):
2289
      raise errors.BlockDeviceError("Invalid meta device size")
2290
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2291
    if not cls._IsValidMeta(meta.dev_path):
2292
      raise errors.BlockDeviceError("Cannot initalize meta device")
2293
    return cls(unique_id, children)
2294

    
2295

    
2296
DEV_MAP = {
2297
  constants.LD_LV: LogicalVolume,
2298
  constants.LD_MD_R1: MDRaid1,
2299
  constants.LD_DRBD7: DRBDev,
2300
  constants.LD_DRBD8: DRBD8,
2301
  }
2302

    
2303

    
2304
def FindDevice(dev_type, unique_id, children):
2305
  """Search for an existing, assembled device.
2306

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

2310
  """
2311
  if dev_type not in DEV_MAP:
2312
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2313
  device = DEV_MAP[dev_type](unique_id, children)
2314
  if not device.Attach():
2315
    return None
2316
  return  device
2317

    
2318

    
2319
def AttachOrAssemble(dev_type, unique_id, children):
2320
  """Try to attach or assemble an existing device.
2321

2322
  This will attach to an existing assembled device or will assemble
2323
  the device, as needed, to bring it fully up.
2324

2325
  """
2326
  if dev_type not in DEV_MAP:
2327
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2328
  device = DEV_MAP[dev_type](unique_id, children)
2329
  if not device.Attach():
2330
    device.Assemble()
2331
    if not device.Attach():
2332
      raise errors.BlockDeviceError("Can't find a valid block device for"
2333
                                    " %s/%s/%s" %
2334
                                    (dev_type, unique_id, children))
2335
  return device
2336

    
2337

    
2338
def Create(dev_type, unique_id, children, size):
2339
  """Create a device.
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].Create(unique_id, children, size)
2345
  return device