Statistics
| Branch: | Tag: | Revision:

root / lib / block / drbd.py @ 239364d0

History | View | Annotate | Download (32.1 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 time
27

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

    
39

    
40
# Size of reads in _CanReadDevice
41

    
42
_DEVICE_READ_SIZE = 128 * 1024
43

    
44

    
45
class DRBD8Dev(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
  def __init__(self, unique_id, children, size, params):
68
    if children and children.count(None) > 0:
69
      children = []
70
    if len(children) not in (0, 2):
71
      raise ValueError("Invalid configuration data %s" % str(children))
72
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
73
      raise ValueError("Invalid configuration data %s" % str(unique_id))
74
    (self._lhost, self._lport,
75
     self._rhost, self._rport,
76
     self._aminor, self._secret) = unique_id
77
    if children:
78
      if not _CanReadDevice(children[1].dev_path):
79
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
80
        children = []
81
    super(DRBD8Dev, self).__init__(unique_id, children, size, params)
82
    self.major = self._DRBD_MAJOR
83

    
84
    drbd_info = DRBD8Info.CreateFromFile()
85
    version = drbd_info.GetVersion()
86
    if version["k_major"] != 8:
87
      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
88
                      " usage: kernel is %s.%s, ganeti wants 8.x",
89
                      version["k_major"], version["k_minor"])
90

    
91
    if version["k_minor"] <= 3:
92
      self._show_info_cls = DRBD83ShowInfo
93
    else:
94
      self._show_info_cls = DRBD84ShowInfo
95

    
96
    self._cmd_gen = self._GetCmdGenerator(drbd_info)
97

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

    
104
  @classmethod
105
  def _GetCmdGenerator(cls, drbd_info):
106
    version = drbd_info.GetVersion()
107
    if version["k_minor"] <= 3:
108
      return drbd_cmdgen.DRBD83CmdGenerator(version)
109
    else:
110
      return drbd_cmdgen.DRBD84CmdGenerator(version)
111

    
112
  @staticmethod
113
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
114
    """Returns DRBD usermode_helper currently set.
115

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

    
130
  @staticmethod
131
  def _DevPath(minor):
132
    """Return the path to a drbd device for a given minor.
133

134
    """
135
    return "/dev/drbd%d" % minor
136

    
137
  @classmethod
138
  def GetUsedDevs(cls):
139
    """Compute the list of used DRBD devices.
140

141
    """
142
    drbd_info = DRBD8Info.CreateFromFile()
143
    return filter(lambda m: not drbd_info.GetMinorStatus(m).is_unconfigured,
144
                  drbd_info.GetMinors())
145

    
146
  def _SetFromMinor(self, minor):
147
    """Set our parameters based on the given minor.
148

149
    This sets our minor variable and our dev_path.
150

151
    """
152
    if minor is None:
153
      self.minor = self.dev_path = None
154
      self.attached = False
155
    else:
156
      self.minor = minor
157
      self.dev_path = self._DevPath(minor)
158
      self.attached = True
159

    
160
  @staticmethod
161
  def _CheckMetaSize(meta_device):
162
    """Check if the given meta device looks like a valid one.
163

164
    This currently only checks the size, which must be around
165
    128MiB.
166

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

    
190
  @classmethod
191
  def _InitMeta(cls, minor, dev_path):
192
    """Initialize a meta device.
193

194
    This will not work if the given minor is in use.
195

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

    
208
    drbd_info = DRBD8Info.CreateFromFile()
209
    cmd_gen = cls._GetCmdGenerator(drbd_info)
210
    cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
211

    
212
    result = utils.RunCmd(cmd)
213
    if result.failed:
214
      base.ThrowError("Can't initialize meta device: %s", result.output)
215

    
216
  def _FindUnusedMinor(self):
217
    """Find an unused DRBD device.
218

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

222
    """
223

    
224
    highest = None
225
    drbd_info = DRBD8Info.CreateFromFile()
226
    for minor in drbd_info.GetMinors():
227
      status = drbd_info.GetMinorStatus(minor)
228
      if not status.is_in_use:
229
        return minor
230
      highest = max(highest, minor)
231

    
232
    if highest is None: # there are no minors in use at all
233
      return 0
234
    if highest >= self._MAX_MINORS:
235
      logging.error("Error: no free drbd minors!")
236
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
237

    
238
    return highest + 1
239

    
240
  def _GetShowData(self, minor):
241
    """Return the `drbdsetup show` data for a minor.
242

243
    """
244
    result = utils.RunCmd(self._cmd_gen.GenShowCmd(minor))
245
    if result.failed:
246
      logging.error("Can't display the drbd config: %s - %s",
247
                    result.fail_reason, result.output)
248
      return None
249
    return result.stdout
250

    
251
  def _GetShowInfo(self, minor):
252
    return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
253

    
254
  def _MatchesLocal(self, info):
255
    """Test if our local config matches with an existing device.
256

257
    The parameter should be as returned from `_GetDevInfo()`. This
258
    method tests if our local backing device is the same as the one in
259
    the info parameter, in effect testing if we look like the given
260
    device.
261

262
    """
263
    if self._children:
264
      backend, meta = self._children
265
    else:
266
      backend = meta = None
267

    
268
    if backend is not None:
269
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
270
    else:
271
      retval = ("local_dev" not in info)
272

    
273
    if meta is not None:
274
      retval = retval and ("meta_dev" in info and
275
                           info["meta_dev"] == meta.dev_path)
276
      retval = retval and ("meta_index" in info and
277
                           info["meta_index"] == 0)
278
    else:
279
      retval = retval and ("meta_dev" not in info and
280
                           "meta_index" not in info)
281
    return retval
282

    
283
  def _MatchesNet(self, info):
284
    """Test if our network config matches with an existing device.
285

286
    The parameter should be as returned from `_GetDevInfo()`. This
287
    method tests if our network configuration is the same as the one
288
    in the info parameter, in effect testing if we look like the given
289
    device.
290

291
    """
292
    if (((self._lhost is None and not ("local_addr" in info)) and
293
         (self._rhost is None and not ("remote_addr" in info)))):
294
      return True
295

    
296
    if self._lhost is None:
297
      return False
298

    
299
    if not ("local_addr" in info and
300
            "remote_addr" in info):
301
      return False
302

    
303
    retval = (info["local_addr"] == (self._lhost, self._lport))
304
    retval = (retval and
305
              info["remote_addr"] == (self._rhost, self._rport))
306
    return retval
307

    
308
  def _AssembleLocal(self, minor, backend, meta, size):
309
    """Configure the local part of a DRBD device.
310

311
    """
312
    cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta,
313
                                          size, self.params)
314

    
315
    for cmd in cmds:
316
      result = utils.RunCmd(cmd)
317
      if result.failed:
318
        base.ThrowError("drbd%d: can't attach local disk: %s",
319
                        minor, result.output)
320

    
321
  def _AssembleNet(self, minor, net_info, protocol,
322
                   dual_pri=False, hmac=None, secret=None):
323
    """Configure the network part of the device.
324

325
    """
326
    lhost, lport, rhost, rport = net_info
327
    if None in net_info:
328
      # we don't want network connection and actually want to make
329
      # sure its shutdown
330
      self._ShutdownNet(minor)
331
      return
332

    
333
    # Workaround for a race condition. When DRBD is doing its dance to
334
    # establish a connection with its peer, it also sends the
335
    # synchronization speed over the wire. In some cases setting the
336
    # sync speed only after setting up both sides can race with DRBD
337
    # connecting, hence we set it here before telling DRBD anything
338
    # about its peer.
339
    sync_errors = self._SetMinorSyncParams(minor, self.params)
340
    if sync_errors:
341
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
342
                      (minor, utils.CommaJoin(sync_errors)))
343

    
344
    family = self._GetNetFamily(minor, lhost, rhost)
345

    
346
    cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport,
347
                                      rhost, rport, protocol,
348
                                      dual_pri, hmac, secret, self.params)
349

    
350
    result = utils.RunCmd(cmd)
351
    if result.failed:
352
      base.ThrowError("drbd%d: can't setup network: %s - %s",
353
                      minor, result.fail_reason, result.output)
354

    
355
    def _CheckNetworkConfig():
356
      info = self._GetShowInfo(minor)
357
      if not "local_addr" in info or not "remote_addr" in info:
358
        raise utils.RetryAgain()
359

    
360
      if (info["local_addr"] != (lhost, lport) or
361
          info["remote_addr"] != (rhost, rport)):
362
        raise utils.RetryAgain()
363

    
364
    try:
365
      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
366
    except utils.RetryTimeout:
367
      base.ThrowError("drbd%d: timeout while configuring network", minor)
368

    
369
  @staticmethod
370
  def _GetNetFamily(minor, lhost, rhost):
371
    if netutils.IP6Address.IsValid(lhost):
372
      if not netutils.IP6Address.IsValid(rhost):
373
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
374
                        (minor, lhost, rhost))
375
      return "ipv6"
376
    elif netutils.IP4Address.IsValid(lhost):
377
      if not netutils.IP4Address.IsValid(rhost):
378
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
379
                        (minor, lhost, rhost))
380
      return "ipv4"
381
    else:
382
      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
383

    
384
  def AddChildren(self, devices):
385
    """Add a disk to the DRBD device.
386

387
    """
388
    if self.minor is None:
389
      base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
390
                      self._aminor)
391
    if len(devices) != 2:
392
      base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
393
    info = self._GetShowInfo(self.minor)
394
    if "local_dev" in info:
395
      base.ThrowError("drbd%d: already attached to a local disk", self.minor)
396
    backend, meta = devices
397
    if backend.dev_path is None or meta.dev_path is None:
398
      base.ThrowError("drbd%d: children not ready during AddChildren",
399
                      self.minor)
400
    backend.Open()
401
    meta.Open()
402
    self._CheckMetaSize(meta.dev_path)
403
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
404

    
405
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
406
    self._children = devices
407

    
408
  def RemoveChildren(self, devices):
409
    """Detach the drbd device from local storage.
410

411
    """
412
    if self.minor is None:
413
      base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
414
                      self._aminor)
415
    # early return if we don't actually have backing storage
416
    info = self._GetShowInfo(self.minor)
417
    if "local_dev" not in info:
418
      return
419
    if len(self._children) != 2:
420
      base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
421
                      self._children)
422
    if self._children.count(None) == 2: # we don't actually have children :)
423
      logging.warning("drbd%d: requested detach while detached", self.minor)
424
      return
425
    if len(devices) != 2:
426
      base.ThrowError("drbd%d: we need two children in RemoveChildren",
427
                      self.minor)
428
    for child, dev in zip(self._children, devices):
429
      if dev != child.dev_path:
430
        base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
431
                        " RemoveChildren", self.minor, dev, child.dev_path)
432

    
433
    self._ShutdownLocal(self.minor)
434
    self._children = []
435

    
436
  def _SetMinorSyncParams(self, minor, params):
437
    """Set the parameters of the DRBD syncer.
438

439
    This is the low-level implementation.
440

441
    @type minor: int
442
    @param minor: the drbd minor whose settings we change
443
    @type params: dict
444
    @param params: LD level disk parameters related to the synchronization
445
    @rtype: list
446
    @return: a list of error messages
447

448
    """
449
    cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
450
    result = utils.RunCmd(cmd)
451
    if result.failed:
452
      msg = ("Can't change syncer rate: %s - %s" %
453
             (result.fail_reason, result.output))
454
      logging.error(msg)
455
      return [msg]
456

    
457
    return []
458

    
459
  def SetSyncParams(self, params):
460
    """Set the synchronization parameters of the DRBD syncer.
461

462
    @type params: dict
463
    @param params: LD level disk parameters related to the synchronization
464
    @rtype: list
465
    @return: a list of error messages, emitted both by the current node and by
466
    children. An empty list means no errors
467

468
    """
469
    if self.minor is None:
470
      err = "Not attached during SetSyncParams"
471
      logging.info(err)
472
      return [err]
473

    
474
    children_result = super(DRBD8Dev, self).SetSyncParams(params)
475
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
476
    return children_result
477

    
478
  def PauseResumeSync(self, pause):
479
    """Pauses or resumes the sync of a DRBD device.
480

481
    @param pause: Wether to pause or resume
482
    @return: the success of the operation
483

484
    """
485
    if self.minor is None:
486
      logging.info("Not attached during PauseSync")
487
      return False
488

    
489
    children_result = super(DRBD8Dev, self).PauseResumeSync(pause)
490

    
491
    if pause:
492
      cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
493
    else:
494
      cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
495

    
496
    result = utils.RunCmd(cmd)
497
    if result.failed:
498
      logging.error("Can't %s: %s - %s", cmd,
499
                    result.fail_reason, result.output)
500
    return not result.failed and children_result
501

    
502
  def GetProcStatus(self):
503
    """Return device data from /proc.
504

505
    """
506
    if self.minor is None:
507
      base.ThrowError("drbd%d: GetStats() called while not attached",
508
                      self._aminor)
509
    drbd_info = DRBD8Info.CreateFromFile()
510
    if not drbd_info.HasMinorStatus(self.minor):
511
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
512
    return drbd_info.GetMinorStatus(self.minor)
513

    
514
  def GetSyncStatus(self):
515
    """Returns the sync status of the device.
516

517

518
    If sync_percent is None, it means all is ok
519
    If estimated_time is None, it means we can't estimate
520
    the time needed, otherwise it's the time left in seconds.
521

522

523
    We set the is_degraded parameter to True on two conditions:
524
    network not connected or local disk missing.
525

526
    We compute the ldisk parameter based on whether we have a local
527
    disk or not.
528

529
    @rtype: objects.BlockDevStatus
530

531
    """
532
    if self.minor is None and not self.Attach():
533
      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
534

    
535
    stats = self.GetProcStatus()
536
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
537

    
538
    if stats.is_disk_uptodate:
539
      ldisk_status = constants.LDS_OKAY
540
    elif stats.is_diskless:
541
      ldisk_status = constants.LDS_FAULTY
542
    else:
543
      ldisk_status = constants.LDS_UNKNOWN
544

    
545
    return objects.BlockDevStatus(dev_path=self.dev_path,
546
                                  major=self.major,
547
                                  minor=self.minor,
548
                                  sync_percent=stats.sync_percent,
549
                                  estimated_time=stats.est_time,
550
                                  is_degraded=is_degraded,
551
                                  ldisk_status=ldisk_status)
552

    
553
  def Open(self, force=False):
554
    """Make the local state primary.
555

556
    If the 'force' parameter is given, the '-o' option is passed to
557
    drbdsetup. Since this is a potentially dangerous operation, the
558
    force flag should be only given after creation, when it actually
559
    is mandatory.
560

561
    """
562
    if self.minor is None and not self.Attach():
563
      logging.error("DRBD cannot attach to a device during open")
564
      return False
565

    
566
    cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
567

    
568
    result = utils.RunCmd(cmd)
569
    if result.failed:
570
      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
571
                      result.output)
572

    
573
  def Close(self):
574
    """Make the local state secondary.
575

576
    This will, of course, fail if the device is in use.
577

578
    """
579
    if self.minor is None and not self.Attach():
580
      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
581
    cmd = self._cmd_gen.GenSecondaryCmd(self.minor)
582
    result = utils.RunCmd(cmd)
583
    if result.failed:
584
      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
585
                      self.minor, result.output)
586

    
587
  def DisconnectNet(self):
588
    """Removes network configuration.
589

590
    This method shutdowns the network side of the device.
591

592
    The method will wait up to a hardcoded timeout for the device to
593
    go into standalone after the 'disconnect' command before
594
    re-configuring it, as sometimes it takes a while for the
595
    disconnect to actually propagate and thus we might issue a 'net'
596
    command while the device is still connected. If the device will
597
    still be attached to the network and we time out, we raise an
598
    exception.
599

600
    """
601
    if self.minor is None:
602
      base.ThrowError("drbd%d: disk not attached in re-attach net",
603
                      self._aminor)
604

    
605
    if None in (self._lhost, self._lport, self._rhost, self._rport):
606
      base.ThrowError("drbd%d: DRBD disk missing network info in"
607
                      " DisconnectNet()", self.minor)
608

    
609
    class _DisconnectStatus:
610
      def __init__(self, ever_disconnected):
611
        self.ever_disconnected = ever_disconnected
612

    
613
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
614

    
615
    def _WaitForDisconnect():
616
      if self.GetProcStatus().is_standalone:
617
        return
618

    
619
      # retry the disconnect, it seems possible that due to a well-time
620
      # disconnect on the peer, my disconnect command might be ignored and
621
      # forgotten
622
      dstatus.ever_disconnected = \
623
        base.IgnoreError(self._ShutdownNet, self.minor) or \
624
        dstatus.ever_disconnected
625

    
626
      raise utils.RetryAgain()
627

    
628
    # Keep start time
629
    start_time = time.time()
630

    
631
    try:
632
      # Start delay at 100 milliseconds and grow up to 2 seconds
633
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
634
                  self._NET_RECONFIG_TIMEOUT)
635
    except utils.RetryTimeout:
636
      if dstatus.ever_disconnected:
637
        msg = ("drbd%d: device did not react to the"
638
               " 'disconnect' command in a timely manner")
639
      else:
640
        msg = "drbd%d: can't shutdown network, even after multiple retries"
641

    
642
      base.ThrowError(msg, self.minor)
643

    
644
    reconfig_time = time.time() - start_time
645
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
646
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
647
                   self.minor, reconfig_time)
648

    
649
  def AttachNet(self, multimaster):
650
    """Reconnects the network.
651

652
    This method connects the network side of the device with a
653
    specified multi-master flag. The device needs to be 'Standalone'
654
    but have valid network configuration data.
655

656
    Args:
657
      - multimaster: init the network in dual-primary mode
658

659
    """
660
    if self.minor is None:
661
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
662

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

    
666
    status = self.GetProcStatus()
667

    
668
    if not status.is_standalone:
669
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
670
                      self.minor)
671

    
672
    self._AssembleNet(self.minor,
673
                      (self._lhost, self._lport, self._rhost, self._rport),
674
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
675
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
676

    
677
  def Attach(self):
678
    """Check if our minor is configured.
679

680
    This doesn't do any device configurations - it only checks if the
681
    minor is in a state different from Unconfigured.
682

683
    Note that this function will not change the state of the system in
684
    any way (except in case of side-effects caused by reading from
685
    /proc).
686

687
    """
688
    used_devs = self.GetUsedDevs()
689
    if self._aminor in used_devs:
690
      minor = self._aminor
691
    else:
692
      minor = None
693

    
694
    self._SetFromMinor(minor)
695
    return minor is not None
696

    
697
  def Assemble(self):
698
    """Assemble the drbd.
699

700
    Method:
701
      - if we have a configured device, we try to ensure that it matches
702
        our config
703
      - if not, we create it from zero
704
      - anyway, set the device parameters
705

706
    """
707
    super(DRBD8Dev, self).Assemble()
708

    
709
    self.Attach()
710
    if self.minor is None:
711
      # local device completely unconfigured
712
      self._FastAssemble()
713
    else:
714
      # we have to recheck the local and network status and try to fix
715
      # the device
716
      self._SlowAssemble()
717

    
718
    sync_errors = self.SetSyncParams(self.params)
719
    if sync_errors:
720
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
721
                      (self.minor, utils.CommaJoin(sync_errors)))
722

    
723
  def _SlowAssemble(self):
724
    """Assembles the DRBD device from a (partially) configured device.
725

726
    In case of partially attached (local device matches but no network
727
    setup), we perform the network attach. If successful, we re-test
728
    the attach if can return success.
729

730
    """
731
    # TODO: Rewrite to not use a for loop just because there is 'break'
732
    # pylint: disable=W0631
733
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
734
    for minor in (self._aminor,):
735
      info = self._GetShowInfo(minor)
736
      match_l = self._MatchesLocal(info)
737
      match_r = self._MatchesNet(info)
738

    
739
      if match_l and match_r:
740
        # everything matches
741
        break
742

    
743
      if match_l and not match_r and "local_addr" not in info:
744
        # disk matches, but not attached to network, attach and recheck
745
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
746
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
747
        if self._MatchesNet(self._GetShowInfo(minor)):
748
          break
749
        else:
750
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
751
                          " show' disagrees", minor)
752

    
753
      if match_r and "local_dev" not in info:
754
        # no local disk, but network attached and it matches
755
        self._AssembleLocal(minor, self._children[0].dev_path,
756
                            self._children[1].dev_path, self.size)
757
        if self._MatchesNet(self._GetShowInfo(minor)):
758
          break
759
        else:
760
          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
761
                          " show' disagrees", minor)
762

    
763
      # this case must be considered only if we actually have local
764
      # storage, i.e. not in diskless mode, because all diskless
765
      # devices are equal from the point of view of local
766
      # configuration
767
      if (match_l and "local_dev" in info and
768
          not match_r and "local_addr" in info):
769
        # strange case - the device network part points to somewhere
770
        # else, even though its local storage is ours; as we own the
771
        # drbd space, we try to disconnect from the remote peer and
772
        # reconnect to our correct one
773
        try:
774
          self._ShutdownNet(minor)
775
        except errors.BlockDeviceError, err:
776
          base.ThrowError("drbd%d: device has correct local storage, wrong"
777
                          " remote peer and is unable to disconnect in order"
778
                          " to attach to the correct peer: %s", minor, str(err))
779
        # note: _AssembleNet also handles the case when we don't want
780
        # local storage (i.e. one or more of the _[lr](host|port) is
781
        # None)
782
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
783
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
784
        if self._MatchesNet(self._GetShowInfo(minor)):
785
          break
786
        else:
787
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
788
                          " show' disagrees", minor)
789

    
790
    else:
791
      minor = None
792

    
793
    self._SetFromMinor(minor)
794
    if minor is None:
795
      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
796
                      self._aminor)
797

    
798
  def _FastAssemble(self):
799
    """Assemble the drbd device from zero.
800

801
    This is run when in Assemble we detect our minor is unused.
802

803
    """
804
    minor = self._aminor
805
    if self._children and self._children[0] and self._children[1]:
806
      self._AssembleLocal(minor, self._children[0].dev_path,
807
                          self._children[1].dev_path, self.size)
808
    if self._lhost and self._lport and self._rhost and self._rport:
809
      self._AssembleNet(minor,
810
                        (self._lhost, self._lport, self._rhost, self._rport),
811
                        constants.DRBD_NET_PROTOCOL,
812
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
813
    self._SetFromMinor(minor)
814

    
815
  def _ShutdownLocal(self, minor):
816
    """Detach from the local device.
817

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

821
    """
822
    cmd = self._cmd_gen.GenDetachCmd(minor)
823
    result = utils.RunCmd(cmd)
824
    if result.failed:
825
      base.ThrowError("drbd%d: can't detach local disk: %s",
826
                      minor, result.output)
827

    
828
  def _ShutdownNet(self, minor):
829
    """Disconnect from the remote peer.
830

831
    This fails if we don't have a local device.
832

833
    """
834
    family = self._GetNetFamily(minor, self._lhost, self._rhost)
835
    cmd = self._cmd_gen.GenDisconnectCmd(minor, family,
836
                                         self._lhost, self._lport,
837
                                         self._rhost, self._rport)
838
    result = utils.RunCmd(cmd)
839
    if result.failed:
840
      base.ThrowError("drbd%d: can't shutdown network: %s",
841
                      minor, result.output)
842

    
843
  @classmethod
844
  def _ShutdownAll(cls, minor):
845
    """Deactivate the device.
846

847
    This will, of course, fail if the device is in use.
848

849
    """
850
    # FIXME: _ShutdownAll, despite being private, is used in nodemaint.py.
851
    # That's why we can't make it an instance method, which in turn requires
852
    # us to duplicate code here (from __init__). This should be properly fixed.
853
    drbd_info = DRBD8Info.CreateFromFile()
854
    cmd_gen = cls._GetCmdGenerator(drbd_info)
855

    
856
    cmd = cmd_gen.GenDownCmd(minor)
857
    result = utils.RunCmd(cmd)
858
    if result.failed:
859
      base.ThrowError("drbd%d: can't shutdown drbd device: %s",
860
                      minor, result.output)
861

    
862
  def Shutdown(self):
863
    """Shutdown the DRBD device.
864

865
    """
866
    if self.minor is None and not self.Attach():
867
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
868
      return
869
    minor = self.minor
870
    self.minor = None
871
    self.dev_path = None
872
    self._ShutdownAll(minor)
873

    
874
  def Remove(self):
875
    """Stub remove for DRBD devices.
876

877
    """
878
    self.Shutdown()
879

    
880
  def Rename(self, new_id):
881
    """Rename a device.
882

883
    This is not supported for drbd devices.
884

885
    """
886
    raise errors.ProgrammerError("Can't rename a drbd device")
887

    
888
  @classmethod
889
  def Create(cls, unique_id, children, size, params, excl_stor):
890
    """Create a new DRBD8 device.
891

892
    Since DRBD devices are not created per se, just assembled, this
893
    function only initializes the metadata.
894

895
    """
896
    if len(children) != 2:
897
      raise errors.ProgrammerError("Invalid setup for the drbd device")
898
    if excl_stor:
899
      raise errors.ProgrammerError("DRBD device requested with"
900
                                   " exclusive_storage")
901
    # check that the minor is unused
902
    aminor = unique_id[4]
903

    
904
    drbd_info = DRBD8Info.CreateFromFile()
905
    if drbd_info.HasMinorStatus(aminor):
906
      status = drbd_info.GetMinorStatus(aminor)
907
      in_use = status.is_in_use
908
    else:
909
      in_use = False
910
    if in_use:
911
      base.ThrowError("drbd%d: minor is already in use at Create() time",
912
                      aminor)
913
    meta = children[1]
914
    meta.Assemble()
915
    if not meta.Attach():
916
      base.ThrowError("drbd%d: can't attach to meta device '%s'",
917
                      aminor, meta)
918
    cls._CheckMetaSize(meta.dev_path)
919
    cls._InitMeta(aminor, meta.dev_path)
920
    return cls(unique_id, children, size, params)
921

    
922
  def Grow(self, amount, dryrun, backingstore):
923
    """Resize the DRBD device and its backing storage.
924

925
    """
926
    if self.minor is None:
927
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
928
    if len(self._children) != 2 or None in self._children:
929
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
930
    self._children[0].Grow(amount, dryrun, backingstore)
931
    if dryrun or backingstore:
932
      # DRBD does not support dry-run mode and is not backing storage,
933
      # so we'll return here
934
      return
935
    cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
936
    result = utils.RunCmd(cmd)
937
    if result.failed:
938
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
939

    
940

    
941
def _CanReadDevice(path):
942
  """Check if we can read from the given device.
943

944
  This tries to read the first 128k of the device.
945

946
  """
947
  try:
948
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
949
    return True
950
  except EnvironmentError:
951
    logging.warning("Can't read from device %s", path, exc_info=True)
952
    return False