Statistics
| Branch: | Tag: | Revision:

root / lib / block / drbd.py @ 09a78e1c

History | View | Annotate | Download (31.8 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 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
  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(DRBD8, 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
      self._cmd_gen = drbd_cmdgen.DRBD83CmdGenerator(drbd_info)
94
    else:
95
      self._show_info_cls = DRBD84ShowInfo
96
      # FIXME: use proper command generator!
97
      self._cmd_gen = None
98

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

    
105
  @staticmethod
106
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
107
    """Returns DRBD usermode_helper currently set.
108

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

    
123
  @staticmethod
124
  def _DevPath(minor):
125
    """Return the path to a drbd device for a given minor.
126

127
    """
128
    return "/dev/drbd%d" % minor
129

    
130
  @classmethod
131
  def GetUsedDevs(cls):
132
    """Compute the list of used DRBD devices.
133

134
    """
135
    drbd_info = DRBD8Info.CreateFromFile()
136
    return filter(lambda m: not drbd_info.GetMinorStatus(m).is_unconfigured,
137
                  drbd_info.GetMinors())
138

    
139
  def _SetFromMinor(self, minor):
140
    """Set our parameters based on the given minor.
141

142
    This sets our minor variable and our dev_path.
143

144
    """
145
    if minor is None:
146
      self.minor = self.dev_path = None
147
      self.attached = False
148
    else:
149
      self.minor = minor
150
      self.dev_path = self._DevPath(minor)
151
      self.attached = True
152

    
153
  @staticmethod
154
  def _CheckMetaSize(meta_device):
155
    """Check if the given meta device looks like a valid one.
156

157
    This currently only checks the size, which must be around
158
    128MiB.
159

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

    
183
  @classmethod
184
  def _InitMeta(cls, minor, dev_path):
185
    """Initialize a meta device.
186

187
    This will not work if the given minor is in use.
188

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

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

    
206
  def _FindUnusedMinor(self):
207
    """Find an unused DRBD device.
208

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

212
    """
213

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

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

    
228
    return highest + 1
229

    
230
  def _GetShowData(self, minor):
231
    """Return the `drbdsetup show` data for a minor.
232

233
    """
234
    result = utils.RunCmd(self._cmd_gen.GenShowCmd(minor))
235
    if result.failed:
236
      logging.error("Can't display the drbd config: %s - %s",
237
                    result.fail_reason, result.output)
238
      return None
239
    return result.stdout
240

    
241
  def _GetShowInfo(self, minor):
242
    return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
243

    
244
  def _MatchesLocal(self, info):
245
    """Test if our local config matches with an existing device.
246

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

252
    """
253
    if self._children:
254
      backend, meta = self._children
255
    else:
256
      backend = meta = None
257

    
258
    if backend is not None:
259
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
260
    else:
261
      retval = ("local_dev" not in info)
262

    
263
    if meta is not None:
264
      retval = retval and ("meta_dev" in info and
265
                           info["meta_dev"] == meta.dev_path)
266
      retval = retval and ("meta_index" in info and
267
                           info["meta_index"] == 0)
268
    else:
269
      retval = retval and ("meta_dev" not in info and
270
                           "meta_index" not in info)
271
    return retval
272

    
273
  def _MatchesNet(self, info):
274
    """Test if our network config matches with an existing device.
275

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

281
    """
282
    if (((self._lhost is None and not ("local_addr" in info)) and
283
         (self._rhost is None and not ("remote_addr" in info)))):
284
      return True
285

    
286
    if self._lhost is None:
287
      return False
288

    
289
    if not ("local_addr" in info and
290
            "remote_addr" in info):
291
      return False
292

    
293
    retval = (info["local_addr"] == (self._lhost, self._lport))
294
    retval = (retval and
295
              info["remote_addr"] == (self._rhost, self._rport))
296
    return retval
297

    
298
  def _AssembleLocal(self, minor, backend, meta, size):
299
    """Configure the local part of a DRBD device.
300

301
    """
302
    cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta,
303
                                          size, self.params)
304

    
305
    for cmd in cmds:
306
      result = utils.RunCmd(cmd)
307
      if result.failed:
308
        base.ThrowError("drbd%d: can't attach local disk: %s",
309
                        minor, result.output)
310

    
311
  def _AssembleNet(self, minor, net_info, protocol,
312
                   dual_pri=False, hmac=None, secret=None):
313
    """Configure the network part of the device.
314

315
    """
316
    lhost, lport, rhost, rport = net_info
317
    if None in net_info:
318
      # we don't want network connection and actually want to make
319
      # sure its shutdown
320
      self._ShutdownNet(minor)
321
      return
322

    
323
    # Workaround for a race condition. When DRBD is doing its dance to
324
    # establish a connection with its peer, it also sends the
325
    # synchronization speed over the wire. In some cases setting the
326
    # sync speed only after setting up both sides can race with DRBD
327
    # connecting, hence we set it here before telling DRBD anything
328
    # about its peer.
329
    sync_errors = self._SetMinorSyncParams(minor, self.params)
330
    if sync_errors:
331
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
332
                      (minor, utils.CommaJoin(sync_errors)))
333

    
334
    if netutils.IP6Address.IsValid(lhost):
335
      if not netutils.IP6Address.IsValid(rhost):
336
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
337
                        (minor, lhost, rhost))
338
      family = "ipv6"
339
    elif netutils.IP4Address.IsValid(lhost):
340
      if not netutils.IP4Address.IsValid(rhost):
341
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
342
                        (minor, lhost, rhost))
343
      family = "ipv4"
344
    else:
345
      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
346

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

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

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

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

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

    
370
  def AddChildren(self, devices):
371
    """Add a disk to the DRBD device.
372

373
    """
374
    if self.minor is None:
375
      base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
376
                      self._aminor)
377
    if len(devices) != 2:
378
      base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
379
    info = self._GetShowInfo(self.minor)
380
    if "local_dev" in info:
381
      base.ThrowError("drbd%d: already attached to a local disk", self.minor)
382
    backend, meta = devices
383
    if backend.dev_path is None or meta.dev_path is None:
384
      base.ThrowError("drbd%d: children not ready during AddChildren",
385
                      self.minor)
386
    backend.Open()
387
    meta.Open()
388
    self._CheckMetaSize(meta.dev_path)
389
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
390

    
391
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
392
    self._children = devices
393

    
394
  def RemoveChildren(self, devices):
395
    """Detach the drbd device from local storage.
396

397
    """
398
    if self.minor is None:
399
      base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
400
                      self._aminor)
401
    # early return if we don't actually have backing storage
402
    info = self._GetShowInfo(self.minor)
403
    if "local_dev" not in info:
404
      return
405
    if len(self._children) != 2:
406
      base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
407
                      self._children)
408
    if self._children.count(None) == 2: # we don't actually have children :)
409
      logging.warning("drbd%d: requested detach while detached", self.minor)
410
      return
411
    if len(devices) != 2:
412
      base.ThrowError("drbd%d: we need two children in RemoveChildren",
413
                      self.minor)
414
    for child, dev in zip(self._children, devices):
415
      if dev != child.dev_path:
416
        base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
417
                        " RemoveChildren", self.minor, dev, child.dev_path)
418

    
419
    self._ShutdownLocal(self.minor)
420
    self._children = []
421

    
422
  def _SetMinorSyncParams(self, minor, params):
423
    """Set the parameters of the DRBD syncer.
424

425
    This is the low-level implementation.
426

427
    @type minor: int
428
    @param minor: the drbd minor whose settings we change
429
    @type params: dict
430
    @param params: LD level disk parameters related to the synchronization
431
    @rtype: list
432
    @return: a list of error messages
433

434
    """
435
    cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
436
    result = utils.RunCmd(cmd)
437
    if result.failed:
438
      msg = ("Can't change syncer rate: %s - %s" %
439
             (result.fail_reason, result.output))
440
      logging.error(msg)
441
      return [msg]
442

    
443
    return []
444

    
445
  def SetSyncParams(self, params):
446
    """Set the synchronization parameters of the DRBD syncer.
447

448
    @type params: dict
449
    @param params: LD level disk parameters related to the synchronization
450
    @rtype: list
451
    @return: a list of error messages, emitted both by the current node and by
452
    children. An empty list means no errors
453

454
    """
455
    if self.minor is None:
456
      err = "Not attached during SetSyncParams"
457
      logging.info(err)
458
      return [err]
459

    
460
    children_result = super(DRBD8, self).SetSyncParams(params)
461
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
462
    return children_result
463

    
464
  def PauseResumeSync(self, pause):
465
    """Pauses or resumes the sync of a DRBD device.
466

467
    @param pause: Wether to pause or resume
468
    @return: the success of the operation
469

470
    """
471
    if self.minor is None:
472
      logging.info("Not attached during PauseSync")
473
      return False
474

    
475
    children_result = super(DRBD8, self).PauseResumeSync(pause)
476

    
477
    if pause:
478
      cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
479
    else:
480
      cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
481

    
482
    result = utils.RunCmd(cmd)
483
    if result.failed:
484
      logging.error("Can't %s: %s - %s", cmd,
485
                    result.fail_reason, result.output)
486
    return not result.failed and children_result
487

    
488
  def GetProcStatus(self):
489
    """Return device data from /proc.
490

491
    """
492
    if self.minor is None:
493
      base.ThrowError("drbd%d: GetStats() called while not attached",
494
                      self._aminor)
495
    drbd_info = DRBD8Info.CreateFromFile()
496
    if not drbd_info.HasMinorStatus(self.minor):
497
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
498
    return drbd_info.GetMinorStatus(self.minor)
499

    
500
  def GetSyncStatus(self):
501
    """Returns the sync status of the device.
502

503

504
    If sync_percent is None, it means all is ok
505
    If estimated_time is None, it means we can't estimate
506
    the time needed, otherwise it's the time left in seconds.
507

508

509
    We set the is_degraded parameter to True on two conditions:
510
    network not connected or local disk missing.
511

512
    We compute the ldisk parameter based on whether we have a local
513
    disk or not.
514

515
    @rtype: objects.BlockDevStatus
516

517
    """
518
    if self.minor is None and not self.Attach():
519
      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
520

    
521
    stats = self.GetProcStatus()
522
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
523

    
524
    if stats.is_disk_uptodate:
525
      ldisk_status = constants.LDS_OKAY
526
    elif stats.is_diskless:
527
      ldisk_status = constants.LDS_FAULTY
528
    else:
529
      ldisk_status = constants.LDS_UNKNOWN
530

    
531
    return objects.BlockDevStatus(dev_path=self.dev_path,
532
                                  major=self.major,
533
                                  minor=self.minor,
534
                                  sync_percent=stats.sync_percent,
535
                                  estimated_time=stats.est_time,
536
                                  is_degraded=is_degraded,
537
                                  ldisk_status=ldisk_status)
538

    
539
  def Open(self, force=False):
540
    """Make the local state primary.
541

542
    If the 'force' parameter is given, the '-o' option is passed to
543
    drbdsetup. Since this is a potentially dangerous operation, the
544
    force flag should be only given after creation, when it actually
545
    is mandatory.
546

547
    """
548
    if self.minor is None and not self.Attach():
549
      logging.error("DRBD cannot attach to a device during open")
550
      return False
551

    
552
    cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
553

    
554
    result = utils.RunCmd(cmd)
555
    if result.failed:
556
      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
557
                      result.output)
558

    
559
  def Close(self):
560
    """Make the local state secondary.
561

562
    This will, of course, fail if the device is in use.
563

564
    """
565
    if self.minor is None and not self.Attach():
566
      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
567
    cmd = self._cmd_gen.GenSecondaryCmd(self.minor)
568
    result = utils.RunCmd(cmd)
569
    if result.failed:
570
      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
571
                      self.minor, result.output)
572

    
573
  def DisconnectNet(self):
574
    """Removes network configuration.
575

576
    This method shutdowns the network side of the device.
577

578
    The method will wait up to a hardcoded timeout for the device to
579
    go into standalone after the 'disconnect' command before
580
    re-configuring it, as sometimes it takes a while for the
581
    disconnect to actually propagate and thus we might issue a 'net'
582
    command while the device is still connected. If the device will
583
    still be attached to the network and we time out, we raise an
584
    exception.
585

586
    """
587
    if self.minor is None:
588
      base.ThrowError("drbd%d: disk not attached in re-attach net",
589
                      self._aminor)
590

    
591
    if None in (self._lhost, self._lport, self._rhost, self._rport):
592
      base.ThrowError("drbd%d: DRBD disk missing network info in"
593
                      " DisconnectNet()", self.minor)
594

    
595
    class _DisconnectStatus:
596
      def __init__(self, ever_disconnected):
597
        self.ever_disconnected = ever_disconnected
598

    
599
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
600

    
601
    def _WaitForDisconnect():
602
      if self.GetProcStatus().is_standalone:
603
        return
604

    
605
      # retry the disconnect, it seems possible that due to a well-time
606
      # disconnect on the peer, my disconnect command might be ignored and
607
      # forgotten
608
      dstatus.ever_disconnected = \
609
        base.IgnoreError(self._ShutdownNet, self.minor) or \
610
        dstatus.ever_disconnected
611

    
612
      raise utils.RetryAgain()
613

    
614
    # Keep start time
615
    start_time = time.time()
616

    
617
    try:
618
      # Start delay at 100 milliseconds and grow up to 2 seconds
619
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
620
                  self._NET_RECONFIG_TIMEOUT)
621
    except utils.RetryTimeout:
622
      if dstatus.ever_disconnected:
623
        msg = ("drbd%d: device did not react to the"
624
               " 'disconnect' command in a timely manner")
625
      else:
626
        msg = "drbd%d: can't shutdown network, even after multiple retries"
627

    
628
      base.ThrowError(msg, self.minor)
629

    
630
    reconfig_time = time.time() - start_time
631
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
632
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
633
                   self.minor, reconfig_time)
634

    
635
  def AttachNet(self, multimaster):
636
    """Reconnects the network.
637

638
    This method connects the network side of the device with a
639
    specified multi-master flag. The device needs to be 'Standalone'
640
    but have valid network configuration data.
641

642
    Args:
643
      - multimaster: init the network in dual-primary mode
644

645
    """
646
    if self.minor is None:
647
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
648

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

    
652
    status = self.GetProcStatus()
653

    
654
    if not status.is_standalone:
655
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
656
                      self.minor)
657

    
658
    self._AssembleNet(self.minor,
659
                      (self._lhost, self._lport, self._rhost, self._rport),
660
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
661
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
662

    
663
  def Attach(self):
664
    """Check if our minor is configured.
665

666
    This doesn't do any device configurations - it only checks if the
667
    minor is in a state different from Unconfigured.
668

669
    Note that this function will not change the state of the system in
670
    any way (except in case of side-effects caused by reading from
671
    /proc).
672

673
    """
674
    used_devs = self.GetUsedDevs()
675
    if self._aminor in used_devs:
676
      minor = self._aminor
677
    else:
678
      minor = None
679

    
680
    self._SetFromMinor(minor)
681
    return minor is not None
682

    
683
  def Assemble(self):
684
    """Assemble the drbd.
685

686
    Method:
687
      - if we have a configured device, we try to ensure that it matches
688
        our config
689
      - if not, we create it from zero
690
      - anyway, set the device parameters
691

692
    """
693
    super(DRBD8, self).Assemble()
694

    
695
    self.Attach()
696
    if self.minor is None:
697
      # local device completely unconfigured
698
      self._FastAssemble()
699
    else:
700
      # we have to recheck the local and network status and try to fix
701
      # the device
702
      self._SlowAssemble()
703

    
704
    sync_errors = self.SetSyncParams(self.params)
705
    if sync_errors:
706
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
707
                      (self.minor, utils.CommaJoin(sync_errors)))
708

    
709
  def _SlowAssemble(self):
710
    """Assembles the DRBD device from a (partially) configured device.
711

712
    In case of partially attached (local device matches but no network
713
    setup), we perform the network attach. If successful, we re-test
714
    the attach if can return success.
715

716
    """
717
    # TODO: Rewrite to not use a for loop just because there is 'break'
718
    # pylint: disable=W0631
719
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
720
    for minor in (self._aminor,):
721
      info = self._GetShowInfo(minor)
722
      match_l = self._MatchesLocal(info)
723
      match_r = self._MatchesNet(info)
724

    
725
      if match_l and match_r:
726
        # everything matches
727
        break
728

    
729
      if match_l and not match_r and "local_addr" not in info:
730
        # disk matches, but not attached to network, attach and recheck
731
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
732
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
733
        if self._MatchesNet(self._GetShowInfo(minor)):
734
          break
735
        else:
736
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
737
                          " show' disagrees", minor)
738

    
739
      if match_r and "local_dev" not in info:
740
        # no local disk, but network attached and it matches
741
        self._AssembleLocal(minor, self._children[0].dev_path,
742
                            self._children[1].dev_path, self.size)
743
        if self._MatchesNet(self._GetShowInfo(minor)):
744
          break
745
        else:
746
          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
747
                          " show' disagrees", minor)
748

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

    
776
    else:
777
      minor = None
778

    
779
    self._SetFromMinor(minor)
780
    if minor is None:
781
      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
782
                      self._aminor)
783

    
784
  def _FastAssemble(self):
785
    """Assemble the drbd device from zero.
786

787
    This is run when in Assemble we detect our minor is unused.
788

789
    """
790
    minor = self._aminor
791
    if self._children and self._children[0] and self._children[1]:
792
      self._AssembleLocal(minor, self._children[0].dev_path,
793
                          self._children[1].dev_path, self.size)
794
    if self._lhost and self._lport and self._rhost and self._rport:
795
      self._AssembleNet(minor,
796
                        (self._lhost, self._lport, self._rhost, self._rport),
797
                        constants.DRBD_NET_PROTOCOL,
798
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
799
    self._SetFromMinor(minor)
800

    
801
  def _ShutdownLocal(self, minor):
802
    """Detach from the local device.
803

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

807
    """
808
    cmd = self._cmd_gen.GenDetachCmd(minor)
809
    result = utils.RunCmd(cmd)
810
    if result.failed:
811
      base.ThrowError("drbd%d: can't detach local disk: %s",
812
                      minor, result.output)
813

    
814
  def _ShutdownNet(self, minor):
815
    """Disconnect from the remote peer.
816

817
    This fails if we don't have a local device.
818

819
    """
820
    cmd = self._cmd_gen.GenDisconnectCmd(minor)
821
    result = utils.RunCmd(cmd)
822
    if result.failed:
823
      base.ThrowError("drbd%d: can't shutdown network: %s",
824
                      minor, result.output)
825

    
826
  @classmethod
827
  def _ShutdownAll(cls, minor):
828
    """Deactivate the device.
829

830
    This will, of course, fail if the device is in use.
831

832
    """
833
    # FIXME: _ShutdownAll, despite being private, is used in nodemaint.py.
834
    # That's why we can't make it an instance method, which in turn requires
835
    # us to duplicate code here (from __init__). This should be proberly fixed.
836
    drbd_info = DRBD8Info.CreateFromFile()
837
    if drbd_info.GetVersion()["k_minor"] <= 3:
838
      cmd_gen = drbd_cmdgen.DRBD83CmdGenerator(drbd_info)
839
    else:
840
      # FIXME: use proper command generator!
841
      cmd_gen = None
842

    
843
    cmd = cmd_gen.GenDownCmd(minor)
844
    result = utils.RunCmd(cmd)
845
    if result.failed:
846
      base.ThrowError("drbd%d: can't shutdown drbd device: %s",
847
                      minor, result.output)
848

    
849
  def Shutdown(self):
850
    """Shutdown the DRBD device.
851

852
    """
853
    if self.minor is None and not self.Attach():
854
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
855
      return
856
    minor = self.minor
857
    self.minor = None
858
    self.dev_path = None
859
    self._ShutdownAll(minor)
860

    
861
  def Remove(self):
862
    """Stub remove for DRBD devices.
863

864
    """
865
    self.Shutdown()
866

    
867
  def Rename(self, new_id):
868
    """Rename a device.
869

870
    This is not supported for drbd devices.
871

872
    """
873
    raise errors.ProgrammerError("Can't rename a drbd device")
874

    
875
  @classmethod
876
  def Create(cls, unique_id, children, size, params, excl_stor):
877
    """Create a new DRBD8 device.
878

879
    Since DRBD devices are not created per se, just assembled, this
880
    function only initializes the metadata.
881

882
    """
883
    if len(children) != 2:
884
      raise errors.ProgrammerError("Invalid setup for the drbd device")
885
    if excl_stor:
886
      raise errors.ProgrammerError("DRBD device requested with"
887
                                   " exclusive_storage")
888
    # check that the minor is unused
889
    aminor = unique_id[4]
890

    
891
    drbd_info = DRBD8Info.CreateFromFile()
892
    if drbd_info.HasMinorStatus(aminor):
893
      status = drbd_info.GetMinorStatus(aminor)
894
      in_use = status.is_in_use
895
    else:
896
      in_use = False
897
    if in_use:
898
      base.ThrowError("drbd%d: minor is already in use at Create() time",
899
                      aminor)
900
    meta = children[1]
901
    meta.Assemble()
902
    if not meta.Attach():
903
      base.ThrowError("drbd%d: can't attach to meta device '%s'",
904
                      aminor, meta)
905
    cls._CheckMetaSize(meta.dev_path)
906
    cls._InitMeta(aminor, meta.dev_path)
907
    return cls(unique_id, children, size, params)
908

    
909
  def Grow(self, amount, dryrun, backingstore):
910
    """Resize the DRBD device and its backing storage.
911

912
    """
913
    if self.minor is None:
914
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
915
    if len(self._children) != 2 or None in self._children:
916
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
917
    self._children[0].Grow(amount, dryrun, backingstore)
918
    if dryrun or backingstore:
919
      # DRBD does not support dry-run mode and is not backing storage,
920
      # so we'll return here
921
      return
922
    cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
923
    result = utils.RunCmd(cmd)
924
    if result.failed:
925
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
926

    
927

    
928
def _CanReadDevice(path):
929
  """Check if we can read from the given device.
930

931
  This tries to read the first 128k of the device.
932

933
  """
934
  try:
935
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
936
    return True
937
  except EnvironmentError:
938
    logging.warning("Can't read from device %s", path, exc_info=True)
939
    return False