Statistics
| Branch: | Tag: | Revision:

root / lib / block / drbd.py @ 27c7d9c3

History | View | Annotate | Download (36.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 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
"""DRBD block device related functionality"""
23

    
24
import errno
25
import logging
26
import shlex
27
import time
28

    
29
from ganeti import constants
30
from ganeti import utils
31
from ganeti import errors
32
from ganeti import netutils
33
from ganeti import objects
34
from ganeti.block import base
35
from ganeti.block.drbd_info import DRBD8Info
36
from ganeti.block.drbd_info import DRBD83ShowInfo
37
from ganeti.block.drbd_info import DRBD84ShowInfo
38

    
39

    
40
# Size of reads in _CanReadDevice
41

    
42
_DEVICE_READ_SIZE = 128 * 1024
43

    
44

    
45
class DRBD8(base.BlockDev):
46
  """DRBD v8.x block device.
47

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

52
  The unique_id for the drbd device is a (local_ip, local_port,
53
  remote_ip, remote_port, local_minor, secret) tuple, and it must have
54
  two children: the data device and the meta_device. The meta device
55
  is checked for valid size and is zeroed on create.
56

57
  """
58
  _DRBD_MAJOR = 147
59

    
60
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
61

    
62
  _MAX_MINORS = 255
63

    
64
  # timeout constants
65
  _NET_RECONFIG_TIMEOUT = 60
66

    
67
  # command line options for barriers
68
  _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
69
  _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
70
  _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
71
  _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
72

    
73
  def __init__(self, unique_id, children, size, params):
74
    if children and children.count(None) > 0:
75
      children = []
76
    if len(children) not in (0, 2):
77
      raise ValueError("Invalid configuration data %s" % str(children))
78
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
79
      raise ValueError("Invalid configuration data %s" % str(unique_id))
80
    (self._lhost, self._lport,
81
     self._rhost, self._rport,
82
     self._aminor, self._secret) = unique_id
83
    if children:
84
      if not _CanReadDevice(children[1].dev_path):
85
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
86
        children = []
87
    super(DRBD8, self).__init__(unique_id, children, size, params)
88
    self.major = self._DRBD_MAJOR
89

    
90
    drbd_info = DRBD8Info.CreateFromFile()
91
    version = drbd_info.GetVersion()
92
    if version["k_major"] != 8:
93
      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
94
                      " usage: kernel is %s.%s, ganeti wants 8.x",
95
                      version["k_major"], version["k_minor"])
96

    
97
    if version["k_minor"] <= 3:
98
      self._show_info_cls = DRBD83ShowInfo
99
    else:
100
      self._show_info_cls = DRBD84ShowInfo
101

    
102
    if (self._lhost is not None and self._lhost == self._rhost and
103
            self._lport == self._rport):
104
      raise ValueError("Invalid configuration data, same local/remote %s" %
105
                       (unique_id,))
106
    self.Attach()
107

    
108
  @staticmethod
109
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
110
    """Returns DRBD usermode_helper currently set.
111

112
    """
113
    try:
114
      helper = utils.ReadFile(filename).splitlines()[0]
115
    except EnvironmentError, err:
116
      if err.errno == errno.ENOENT:
117
        base.ThrowError("The file %s cannot be opened, check if the module"
118
                        " is loaded (%s)", filename, str(err))
119
      else:
120
        base.ThrowError("Can't read DRBD helper file %s: %s",
121
                        filename, str(err))
122
    if not helper:
123
      base.ThrowError("Can't read any data from %s", filename)
124
    return helper
125

    
126
  @staticmethod
127
  def _DevPath(minor):
128
    """Return the path to a drbd device for a given minor.
129

130
    """
131
    return "/dev/drbd%d" % minor
132

    
133
  @classmethod
134
  def GetUsedDevs(cls):
135
    """Compute the list of used DRBD devices.
136

137
    """
138
    drbd_info = DRBD8Info.CreateFromFile()
139
    return filter(lambda m: not drbd_info.GetMinorStatus(m).is_unconfigured,
140
                  drbd_info.GetMinors())
141

    
142
  def _SetFromMinor(self, minor):
143
    """Set our parameters based on the given minor.
144

145
    This sets our minor variable and our dev_path.
146

147
    """
148
    if minor is None:
149
      self.minor = self.dev_path = None
150
      self.attached = False
151
    else:
152
      self.minor = minor
153
      self.dev_path = self._DevPath(minor)
154
      self.attached = True
155

    
156
  @staticmethod
157
  def _CheckMetaSize(meta_device):
158
    """Check if the given meta device looks like a valid one.
159

160
    This currently only checks the size, which must be around
161
    128MiB.
162

163
    """
164
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
165
    if result.failed:
166
      base.ThrowError("Failed to get device size: %s - %s",
167
                      result.fail_reason, result.output)
168
    try:
169
      sectors = int(result.stdout)
170
    except (TypeError, ValueError):
171
      base.ThrowError("Invalid output from blockdev: '%s'", result.stdout)
172
    num_bytes = sectors * 512
173
    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
174
      base.ThrowError("Meta device too small (%.2fMib)",
175
                      (num_bytes / 1024 / 1024))
176
    # the maximum *valid* size of the meta device when living on top
177
    # of LVM is hard to compute: it depends on the number of stripes
178
    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
179
    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
180
    # size meta device; as such, we restrict it to 1GB (a little bit
181
    # too generous, but making assumptions about PE size is hard)
182
    if num_bytes > 1024 * 1024 * 1024:
183
      base.ThrowError("Meta device too big (%.2fMiB)",
184
                      (num_bytes / 1024 / 1024))
185

    
186
  @classmethod
187
  def _InitMeta(cls, minor, dev_path):
188
    """Initialize a meta device.
189

190
    This will not work if the given minor is in use.
191

192
    """
193
    # Zero the metadata first, in order to make sure drbdmeta doesn't
194
    # try to auto-detect existing filesystems or similar (see
195
    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
196
    # care about the first 128MB of data in the device, even though it
197
    # can be bigger
198
    result = utils.RunCmd([constants.DD_CMD,
199
                           "if=/dev/zero", "of=%s" % dev_path,
200
                           "bs=1048576", "count=128", "oflag=direct"])
201
    if result.failed:
202
      base.ThrowError("Can't wipe the meta device: %s", result.output)
203

    
204
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
205
                           "v08", dev_path, "0", "create-md"])
206
    if result.failed:
207
      base.ThrowError("Can't initialize meta device: %s", result.output)
208

    
209
  def _FindUnusedMinor(self):
210
    """Find an unused DRBD device.
211

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

215
    """
216

    
217
    highest = None
218
    drbd_info = DRBD8Info.CreateFromFile()
219
    for minor in drbd_info.GetMinors():
220
      status = drbd_info.GetMinorStatus(minor)
221
      if not status.is_in_use:
222
        return minor
223
      highest = max(highest, minor)
224

    
225
    if highest is None: # there are no minors in use at all
226
      return 0
227
    if highest >= self._MAX_MINORS:
228
      logging.error("Error: no free drbd minors!")
229
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
230

    
231
    return highest + 1
232

    
233
  @classmethod
234
  def _GetShowData(cls, minor):
235
    """Return the `drbdsetup show` data for a minor.
236

237
    """
238
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
239
    if result.failed:
240
      logging.error("Can't display the drbd config: %s - %s",
241
                    result.fail_reason, result.output)
242
      return None
243
    return result.stdout
244

    
245
  def _GetShowInfo(self, minor):
246
    return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
247

    
248
  def _MatchesLocal(self, info):
249
    """Test if our local config matches with an existing device.
250

251
    The parameter should be as returned from `_GetDevInfo()`. This
252
    method tests if our local backing device is the same as the one in
253
    the info parameter, in effect testing if we look like the given
254
    device.
255

256
    """
257
    if self._children:
258
      backend, meta = self._children
259
    else:
260
      backend = meta = None
261

    
262
    if backend is not None:
263
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
264
    else:
265
      retval = ("local_dev" not in info)
266

    
267
    if meta is not None:
268
      retval = retval and ("meta_dev" in info and
269
                           info["meta_dev"] == meta.dev_path)
270
      retval = retval and ("meta_index" in info and
271
                           info["meta_index"] == 0)
272
    else:
273
      retval = retval and ("meta_dev" not in info and
274
                           "meta_index" not in info)
275
    return retval
276

    
277
  def _MatchesNet(self, info):
278
    """Test if our network config matches with an existing device.
279

280
    The parameter should be as returned from `_GetDevInfo()`. This
281
    method tests if our network configuration is the same as the one
282
    in the info parameter, in effect testing if we look like the given
283
    device.
284

285
    """
286
    if (((self._lhost is None and not ("local_addr" in info)) and
287
         (self._rhost is None and not ("remote_addr" in info)))):
288
      return True
289

    
290
    if self._lhost is None:
291
      return False
292

    
293
    if not ("local_addr" in info and
294
            "remote_addr" in info):
295
      return False
296

    
297
    retval = (info["local_addr"] == (self._lhost, self._lport))
298
    retval = (retval and
299
              info["remote_addr"] == (self._rhost, self._rport))
300
    return retval
301

    
302
  def _AssembleLocal(self, minor, backend, meta, size):
303
    """Configure the local part of a DRBD device.
304

305
    """
306
    args = ["drbdsetup", self._DevPath(minor), "disk",
307
            backend, meta, "0",
308
            "-e", "detach",
309
            "--create-device"]
310
    if size:
311
      args.extend(["-d", "%sm" % size])
312

    
313
    drbd_info = DRBD8Info.CreateFromFile()
314
    version = drbd_info.GetVersion()
315
    vmaj = version["k_major"]
316
    vmin = version["k_minor"]
317
    vrel = version["k_point"]
318

    
319
    barrier_args = \
320
      self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
321
                                   self.params[constants.LDP_BARRIERS],
322
                                   self.params[constants.LDP_NO_META_FLUSH])
323
    args.extend(barrier_args)
324

    
325
    if self.params[constants.LDP_DISK_CUSTOM]:
326
      args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
327

    
328
    result = utils.RunCmd(args)
329
    if result.failed:
330
      base.ThrowError("drbd%d: can't attach local disk: %s",
331
                      minor, result.output)
332

    
333
  @classmethod
334
  def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
335
                              disable_meta_flush):
336
    """Compute the DRBD command line parameters for disk barriers
337

338
    Returns a list of the disk barrier parameters as requested via the
339
    disabled_barriers and disable_meta_flush arguments, and according to the
340
    supported ones in the DRBD version vmaj.vmin.vrel
341

342
    If the desired option is unsupported, raises errors.BlockDeviceError.
343

344
    """
345
    disabled_barriers_set = frozenset(disabled_barriers)
346
    if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
347
      raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
348
                                    " barriers" % disabled_barriers)
349

    
350
    args = []
351

    
352
    # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
353
    # does not exist)
354
    if not vmaj == 8 and vmin in (0, 2, 3):
355
      raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
356
                                    (vmaj, vmin, vrel))
357

    
358
    def _AppendOrRaise(option, min_version):
359
      """Helper for DRBD options"""
360
      if min_version is not None and vrel >= min_version:
361
        args.append(option)
362
      else:
363
        raise errors.BlockDeviceError("Could not use the option %s as the"
364
                                      " DRBD version %d.%d.%d does not support"
365
                                      " it." % (option, vmaj, vmin, vrel))
366

    
367
    # the minimum version for each feature is encoded via pairs of (minor
368
    # version -> x) where x is version in which support for the option was
369
    # introduced.
370
    meta_flush_supported = disk_flush_supported = {
371
      0: 12,
372
      2: 7,
373
      3: 0,
374
      }
375

    
376
    disk_drain_supported = {
377
      2: 7,
378
      3: 0,
379
      }
380

    
381
    disk_barriers_supported = {
382
      3: 0,
383
      }
384

    
385
    # meta flushes
386
    if disable_meta_flush:
387
      _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
388
                     meta_flush_supported.get(vmin, None))
389

    
390
    # disk flushes
391
    if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
392
      _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
393
                     disk_flush_supported.get(vmin, None))
394

    
395
    # disk drain
396
    if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
397
      _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
398
                     disk_drain_supported.get(vmin, None))
399

    
400
    # disk barriers
401
    if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
402
      _AppendOrRaise(cls._DISABLE_DISK_OPTION,
403
                     disk_barriers_supported.get(vmin, None))
404

    
405
    return args
406

    
407
  def _AssembleNet(self, minor, net_info, protocol,
408
                   dual_pri=False, hmac=None, secret=None):
409
    """Configure the network part of the device.
410

411
    """
412
    lhost, lport, rhost, rport = net_info
413
    if None in net_info:
414
      # we don't want network connection and actually want to make
415
      # sure its shutdown
416
      self._ShutdownNet(minor)
417
      return
418

    
419
    # Workaround for a race condition. When DRBD is doing its dance to
420
    # establish a connection with its peer, it also sends the
421
    # synchronization speed over the wire. In some cases setting the
422
    # sync speed only after setting up both sides can race with DRBD
423
    # connecting, hence we set it here before telling DRBD anything
424
    # about its peer.
425
    sync_errors = self._SetMinorSyncParams(minor, self.params)
426
    if sync_errors:
427
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
428
                      (minor, utils.CommaJoin(sync_errors)))
429

    
430
    if netutils.IP6Address.IsValid(lhost):
431
      if not netutils.IP6Address.IsValid(rhost):
432
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
433
                        (minor, lhost, rhost))
434
      family = "ipv6"
435
    elif netutils.IP4Address.IsValid(lhost):
436
      if not netutils.IP4Address.IsValid(rhost):
437
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
438
                        (minor, lhost, rhost))
439
      family = "ipv4"
440
    else:
441
      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
442

    
443
    args = ["drbdsetup", self._DevPath(minor), "net",
444
            "%s:%s:%s" % (family, lhost, lport),
445
            "%s:%s:%s" % (family, rhost, rport), protocol,
446
            "-A", "discard-zero-changes",
447
            "-B", "consensus",
448
            "--create-device",
449
            ]
450
    if dual_pri:
451
      args.append("-m")
452
    if hmac and secret:
453
      args.extend(["-a", hmac, "-x", secret])
454

    
455
    if self.params[constants.LDP_NET_CUSTOM]:
456
      args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
457

    
458
    result = utils.RunCmd(args)
459
    if result.failed:
460
      base.ThrowError("drbd%d: can't setup network: %s - %s",
461
                      minor, result.fail_reason, result.output)
462

    
463
    def _CheckNetworkConfig():
464
      info = self._GetShowInfo(minor)
465
      if not "local_addr" in info or not "remote_addr" in info:
466
        raise utils.RetryAgain()
467

    
468
      if (info["local_addr"] != (lhost, lport) or
469
          info["remote_addr"] != (rhost, rport)):
470
        raise utils.RetryAgain()
471

    
472
    try:
473
      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
474
    except utils.RetryTimeout:
475
      base.ThrowError("drbd%d: timeout while configuring network", minor)
476

    
477
  def AddChildren(self, devices):
478
    """Add a disk to the DRBD device.
479

480
    """
481
    if self.minor is None:
482
      base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
483
                      self._aminor)
484
    if len(devices) != 2:
485
      base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
486
    info = self._GetShowInfo(self.minor)
487
    if "local_dev" in info:
488
      base.ThrowError("drbd%d: already attached to a local disk", self.minor)
489
    backend, meta = devices
490
    if backend.dev_path is None or meta.dev_path is None:
491
      base.ThrowError("drbd%d: children not ready during AddChildren",
492
                      self.minor)
493
    backend.Open()
494
    meta.Open()
495
    self._CheckMetaSize(meta.dev_path)
496
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
497

    
498
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
499
    self._children = devices
500

    
501
  def RemoveChildren(self, devices):
502
    """Detach the drbd device from local storage.
503

504
    """
505
    if self.minor is None:
506
      base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
507
                      self._aminor)
508
    # early return if we don't actually have backing storage
509
    info = self._GetShowInfo(self.minor)
510
    if "local_dev" not in info:
511
      return
512
    if len(self._children) != 2:
513
      base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
514
                      self._children)
515
    if self._children.count(None) == 2: # we don't actually have children :)
516
      logging.warning("drbd%d: requested detach while detached", self.minor)
517
      return
518
    if len(devices) != 2:
519
      base.ThrowError("drbd%d: we need two children in RemoveChildren",
520
                      self.minor)
521
    for child, dev in zip(self._children, devices):
522
      if dev != child.dev_path:
523
        base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
524
                        " RemoveChildren", self.minor, dev, child.dev_path)
525

    
526
    self._ShutdownLocal(self.minor)
527
    self._children = []
528

    
529
  def _SetMinorSyncParams(self, minor, params):
530
    """Set the parameters of the DRBD syncer.
531

532
    This is the low-level implementation.
533

534
    @type minor: int
535
    @param minor: the drbd minor whose settings we change
536
    @type params: dict
537
    @param params: LD level disk parameters related to the synchronization
538
    @rtype: list
539
    @return: a list of error messages
540

541
    """
542

    
543
    args = ["drbdsetup", self._DevPath(minor), "syncer"]
544
    if params[constants.LDP_DYNAMIC_RESYNC]:
545
      drbd_info = DRBD8Info.CreateFromFile()
546
      version = drbd_info.GetVersion()
547
      vmin = version["k_minor"]
548
      vrel = version["k_point"]
549

    
550
      # By definition we are using 8.x, so just check the rest of the version
551
      # number
552
      if vmin != 3 or vrel < 9:
553
        msg = ("The current DRBD version (8.%d.%d) does not support the "
554
               "dynamic resync speed controller" % (vmin, vrel))
555
        logging.error(msg)
556
        return [msg]
557

    
558
      if params[constants.LDP_PLAN_AHEAD] == 0:
559
        msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
560
               " controller at DRBD level. If you want to disable it, please"
561
               " set the dynamic-resync disk parameter to False.")
562
        logging.error(msg)
563
        return [msg]
564

    
565
      # add the c-* parameters to args
566
      args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
567
                   "--c-fill-target", params[constants.LDP_FILL_TARGET],
568
                   "--c-delay-target", params[constants.LDP_DELAY_TARGET],
569
                   "--c-max-rate", params[constants.LDP_MAX_RATE],
570
                   "--c-min-rate", params[constants.LDP_MIN_RATE],
571
                   ])
572

    
573
    else:
574
      args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
575

    
576
    args.append("--create-device")
577
    result = utils.RunCmd(args)
578
    if result.failed:
579
      msg = ("Can't change syncer rate: %s - %s" %
580
             (result.fail_reason, result.output))
581
      logging.error(msg)
582
      return [msg]
583

    
584
    return []
585

    
586
  def SetSyncParams(self, params):
587
    """Set the synchronization parameters of the DRBD syncer.
588

589
    @type params: dict
590
    @param params: LD level disk parameters related to the synchronization
591
    @rtype: list
592
    @return: a list of error messages, emitted both by the current node and by
593
    children. An empty list means no errors
594

595
    """
596
    if self.minor is None:
597
      err = "Not attached during SetSyncParams"
598
      logging.info(err)
599
      return [err]
600

    
601
    children_result = super(DRBD8, self).SetSyncParams(params)
602
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
603
    return children_result
604

    
605
  def PauseResumeSync(self, pause):
606
    """Pauses or resumes the sync of a DRBD device.
607

608
    @param pause: Wether to pause or resume
609
    @return: the success of the operation
610

611
    """
612
    if self.minor is None:
613
      logging.info("Not attached during PauseSync")
614
      return False
615

    
616
    children_result = super(DRBD8, self).PauseResumeSync(pause)
617

    
618
    if pause:
619
      cmd = "pause-sync"
620
    else:
621
      cmd = "resume-sync"
622

    
623
    result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
624
    if result.failed:
625
      logging.error("Can't %s: %s - %s", cmd,
626
                    result.fail_reason, result.output)
627
    return not result.failed and children_result
628

    
629
  def GetProcStatus(self):
630
    """Return device data from /proc.
631

632
    """
633
    if self.minor is None:
634
      base.ThrowError("drbd%d: GetStats() called while not attached",
635
                      self._aminor)
636
    drbd_info = DRBD8Info.CreateFromFile()
637
    if not drbd_info.HasMinorStatus(self.minor):
638
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
639
    return drbd_info.GetMinorStatus(self.minor)
640

    
641
  def GetSyncStatus(self):
642
    """Returns the sync status of the device.
643

644

645
    If sync_percent is None, it means all is ok
646
    If estimated_time is None, it means we can't estimate
647
    the time needed, otherwise it's the time left in seconds.
648

649

650
    We set the is_degraded parameter to True on two conditions:
651
    network not connected or local disk missing.
652

653
    We compute the ldisk parameter based on whether we have a local
654
    disk or not.
655

656
    @rtype: objects.BlockDevStatus
657

658
    """
659
    if self.minor is None and not self.Attach():
660
      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
661

    
662
    stats = self.GetProcStatus()
663
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
664

    
665
    if stats.is_disk_uptodate:
666
      ldisk_status = constants.LDS_OKAY
667
    elif stats.is_diskless:
668
      ldisk_status = constants.LDS_FAULTY
669
    else:
670
      ldisk_status = constants.LDS_UNKNOWN
671

    
672
    return objects.BlockDevStatus(dev_path=self.dev_path,
673
                                  major=self.major,
674
                                  minor=self.minor,
675
                                  sync_percent=stats.sync_percent,
676
                                  estimated_time=stats.est_time,
677
                                  is_degraded=is_degraded,
678
                                  ldisk_status=ldisk_status)
679

    
680
  def Open(self, force=False):
681
    """Make the local state primary.
682

683
    If the 'force' parameter is given, the '-o' option is passed to
684
    drbdsetup. Since this is a potentially dangerous operation, the
685
    force flag should be only given after creation, when it actually
686
    is mandatory.
687

688
    """
689
    if self.minor is None and not self.Attach():
690
      logging.error("DRBD cannot attach to a device during open")
691
      return False
692
    cmd = ["drbdsetup", self.dev_path, "primary"]
693
    if force:
694
      cmd.append("-o")
695
    result = utils.RunCmd(cmd)
696
    if result.failed:
697
      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
698
                      result.output)
699

    
700
  def Close(self):
701
    """Make the local state secondary.
702

703
    This will, of course, fail if the device is in use.
704

705
    """
706
    if self.minor is None and not self.Attach():
707
      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
708
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
709
    if result.failed:
710
      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
711
                      self.minor, result.output)
712

    
713
  def DisconnectNet(self):
714
    """Removes network configuration.
715

716
    This method shutdowns the network side of the device.
717

718
    The method will wait up to a hardcoded timeout for the device to
719
    go into standalone after the 'disconnect' command before
720
    re-configuring it, as sometimes it takes a while for the
721
    disconnect to actually propagate and thus we might issue a 'net'
722
    command while the device is still connected. If the device will
723
    still be attached to the network and we time out, we raise an
724
    exception.
725

726
    """
727
    if self.minor is None:
728
      base.ThrowError("drbd%d: disk not attached in re-attach net",
729
                      self._aminor)
730

    
731
    if None in (self._lhost, self._lport, self._rhost, self._rport):
732
      base.ThrowError("drbd%d: DRBD disk missing network info in"
733
                      " DisconnectNet()", self.minor)
734

    
735
    class _DisconnectStatus:
736
      def __init__(self, ever_disconnected):
737
        self.ever_disconnected = ever_disconnected
738

    
739
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
740

    
741
    def _WaitForDisconnect():
742
      if self.GetProcStatus().is_standalone:
743
        return
744

    
745
      # retry the disconnect, it seems possible that due to a well-time
746
      # disconnect on the peer, my disconnect command might be ignored and
747
      # forgotten
748
      dstatus.ever_disconnected = \
749
        base.IgnoreError(self._ShutdownNet, self.minor) or \
750
        dstatus.ever_disconnected
751

    
752
      raise utils.RetryAgain()
753

    
754
    # Keep start time
755
    start_time = time.time()
756

    
757
    try:
758
      # Start delay at 100 milliseconds and grow up to 2 seconds
759
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
760
                  self._NET_RECONFIG_TIMEOUT)
761
    except utils.RetryTimeout:
762
      if dstatus.ever_disconnected:
763
        msg = ("drbd%d: device did not react to the"
764
               " 'disconnect' command in a timely manner")
765
      else:
766
        msg = "drbd%d: can't shutdown network, even after multiple retries"
767

    
768
      base.ThrowError(msg, self.minor)
769

    
770
    reconfig_time = time.time() - start_time
771
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
772
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
773
                   self.minor, reconfig_time)
774

    
775
  def AttachNet(self, multimaster):
776
    """Reconnects the network.
777

778
    This method connects the network side of the device with a
779
    specified multi-master flag. The device needs to be 'Standalone'
780
    but have valid network configuration data.
781

782
    Args:
783
      - multimaster: init the network in dual-primary mode
784

785
    """
786
    if self.minor is None:
787
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
788

    
789
    if None in (self._lhost, self._lport, self._rhost, self._rport):
790
      base.ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
791

    
792
    status = self.GetProcStatus()
793

    
794
    if not status.is_standalone:
795
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
796
                      self.minor)
797

    
798
    self._AssembleNet(self.minor,
799
                      (self._lhost, self._lport, self._rhost, self._rport),
800
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
801
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
802

    
803
  def Attach(self):
804
    """Check if our minor is configured.
805

806
    This doesn't do any device configurations - it only checks if the
807
    minor is in a state different from Unconfigured.
808

809
    Note that this function will not change the state of the system in
810
    any way (except in case of side-effects caused by reading from
811
    /proc).
812

813
    """
814
    used_devs = self.GetUsedDevs()
815
    if self._aminor in used_devs:
816
      minor = self._aminor
817
    else:
818
      minor = None
819

    
820
    self._SetFromMinor(minor)
821
    return minor is not None
822

    
823
  def Assemble(self):
824
    """Assemble the drbd.
825

826
    Method:
827
      - if we have a configured device, we try to ensure that it matches
828
        our config
829
      - if not, we create it from zero
830
      - anyway, set the device parameters
831

832
    """
833
    super(DRBD8, self).Assemble()
834

    
835
    self.Attach()
836
    if self.minor is None:
837
      # local device completely unconfigured
838
      self._FastAssemble()
839
    else:
840
      # we have to recheck the local and network status and try to fix
841
      # the device
842
      self._SlowAssemble()
843

    
844
    sync_errors = self.SetSyncParams(self.params)
845
    if sync_errors:
846
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
847
                      (self.minor, utils.CommaJoin(sync_errors)))
848

    
849
  def _SlowAssemble(self):
850
    """Assembles the DRBD device from a (partially) configured device.
851

852
    In case of partially attached (local device matches but no network
853
    setup), we perform the network attach. If successful, we re-test
854
    the attach if can return success.
855

856
    """
857
    # TODO: Rewrite to not use a for loop just because there is 'break'
858
    # pylint: disable=W0631
859
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
860
    for minor in (self._aminor,):
861
      info = self._GetShowInfo(minor)
862
      match_l = self._MatchesLocal(info)
863
      match_r = self._MatchesNet(info)
864

    
865
      if match_l and match_r:
866
        # everything matches
867
        break
868

    
869
      if match_l and not match_r and "local_addr" not in info:
870
        # disk matches, but not attached to network, attach and recheck
871
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
872
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
873
        if self._MatchesNet(self._GetShowInfo(minor)):
874
          break
875
        else:
876
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
877
                          " show' disagrees", minor)
878

    
879
      if match_r and "local_dev" not in info:
880
        # no local disk, but network attached and it matches
881
        self._AssembleLocal(minor, self._children[0].dev_path,
882
                            self._children[1].dev_path, self.size)
883
        if self._MatchesNet(self._GetShowInfo(minor)):
884
          break
885
        else:
886
          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
887
                          " show' disagrees", minor)
888

    
889
      # this case must be considered only if we actually have local
890
      # storage, i.e. not in diskless mode, because all diskless
891
      # devices are equal from the point of view of local
892
      # configuration
893
      if (match_l and "local_dev" in info and
894
          not match_r and "local_addr" in info):
895
        # strange case - the device network part points to somewhere
896
        # else, even though its local storage is ours; as we own the
897
        # drbd space, we try to disconnect from the remote peer and
898
        # reconnect to our correct one
899
        try:
900
          self._ShutdownNet(minor)
901
        except errors.BlockDeviceError, err:
902
          base.ThrowError("drbd%d: device has correct local storage, wrong"
903
                          " remote peer and is unable to disconnect in order"
904
                          " to attach to the correct peer: %s", minor, str(err))
905
        # note: _AssembleNet also handles the case when we don't want
906
        # local storage (i.e. one or more of the _[lr](host|port) is
907
        # None)
908
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
909
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
910
        if self._MatchesNet(self._GetShowInfo(minor)):
911
          break
912
        else:
913
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
914
                          " show' disagrees", minor)
915

    
916
    else:
917
      minor = None
918

    
919
    self._SetFromMinor(minor)
920
    if minor is None:
921
      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
922
                      self._aminor)
923

    
924
  def _FastAssemble(self):
925
    """Assemble the drbd device from zero.
926

927
    This is run when in Assemble we detect our minor is unused.
928

929
    """
930
    minor = self._aminor
931
    if self._children and self._children[0] and self._children[1]:
932
      self._AssembleLocal(minor, self._children[0].dev_path,
933
                          self._children[1].dev_path, self.size)
934
    if self._lhost and self._lport and self._rhost and self._rport:
935
      self._AssembleNet(minor,
936
                        (self._lhost, self._lport, self._rhost, self._rport),
937
                        constants.DRBD_NET_PROTOCOL,
938
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
939
    self._SetFromMinor(minor)
940

    
941
  @classmethod
942
  def _ShutdownLocal(cls, minor):
943
    """Detach from the local device.
944

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

948
    """
949
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
950
    if result.failed:
951
      base.ThrowError("drbd%d: can't detach local disk: %s",
952
                      minor, result.output)
953

    
954
  @classmethod
955
  def _ShutdownNet(cls, minor):
956
    """Disconnect from the remote peer.
957

958
    This fails if we don't have a local device.
959

960
    """
961
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
962
    if result.failed:
963
      base.ThrowError("drbd%d: can't shutdown network: %s",
964
                      minor, result.output)
965

    
966
  @classmethod
967
  def _ShutdownAll(cls, minor):
968
    """Deactivate the device.
969

970
    This will, of course, fail if the device is in use.
971

972
    """
973
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
974
    if result.failed:
975
      base.ThrowError("drbd%d: can't shutdown drbd device: %s",
976
                      minor, result.output)
977

    
978
  def Shutdown(self):
979
    """Shutdown the DRBD device.
980

981
    """
982
    if self.minor is None and not self.Attach():
983
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
984
      return
985
    minor = self.minor
986
    self.minor = None
987
    self.dev_path = None
988
    self._ShutdownAll(minor)
989

    
990
  def Remove(self):
991
    """Stub remove for DRBD devices.
992

993
    """
994
    self.Shutdown()
995

    
996
  def Rename(self, new_id):
997
    """Rename a device.
998

999
    This is not supported for drbd devices.
1000

1001
    """
1002
    raise errors.ProgrammerError("Can't rename a drbd device")
1003

    
1004
  @classmethod
1005
  def Create(cls, unique_id, children, size, params, excl_stor):
1006
    """Create a new DRBD8 device.
1007

1008
    Since DRBD devices are not created per se, just assembled, this
1009
    function only initializes the metadata.
1010

1011
    """
1012
    if len(children) != 2:
1013
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1014
    if excl_stor:
1015
      raise errors.ProgrammerError("DRBD device requested with"
1016
                                   " exclusive_storage")
1017
    # check that the minor is unused
1018
    aminor = unique_id[4]
1019

    
1020
    drbd_info = DRBD8Info.CreateFromFile()
1021
    if drbd_info.HasMinorStatus(aminor):
1022
      status = drbd_info.GetMinorStatus(aminor)
1023
      in_use = status.is_in_use
1024
    else:
1025
      in_use = False
1026
    if in_use:
1027
      base.ThrowError("drbd%d: minor is already in use at Create() time",
1028
                      aminor)
1029
    meta = children[1]
1030
    meta.Assemble()
1031
    if not meta.Attach():
1032
      base.ThrowError("drbd%d: can't attach to meta device '%s'",
1033
                      aminor, meta)
1034
    cls._CheckMetaSize(meta.dev_path)
1035
    cls._InitMeta(aminor, meta.dev_path)
1036
    return cls(unique_id, children, size, params)
1037

    
1038
  def Grow(self, amount, dryrun, backingstore):
1039
    """Resize the DRBD device and its backing storage.
1040

1041
    """
1042
    if self.minor is None:
1043
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
1044
    if len(self._children) != 2 or None in self._children:
1045
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
1046
    self._children[0].Grow(amount, dryrun, backingstore)
1047
    if dryrun or backingstore:
1048
      # DRBD does not support dry-run mode and is not backing storage,
1049
      # so we'll return here
1050
      return
1051
    result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1052
                           "%dm" % (self.size + amount)])
1053
    if result.failed:
1054
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1055

    
1056

    
1057
def _CanReadDevice(path):
1058
  """Check if we can read from the given device.
1059

1060
  This tries to read the first 128k of the device.
1061

1062
  """
1063
  try:
1064
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1065
    return True
1066
  except EnvironmentError:
1067
    logging.warning("Can't read from device %s", path, exc_info=True)
1068
    return False