Statistics
| Branch: | Tag: | Revision:

root / lib / storage / drbd.py @ 688b5752

History | View | Annotate | Download (34.7 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.storage import base
34
from ganeti.storage.drbd_info import DRBD8Info
35
from ganeti.storage import drbd_info
36
from ganeti.storage import drbd_cmdgen
37

    
38

    
39
# Size of reads in _CanReadDevice
40

    
41
_DEVICE_READ_SIZE = 128 * 1024
42

    
43

    
44
class DRBD8(object):
45
  """Various methods to deals with the DRBD system as a whole.
46

47
  This class provides a set of methods to deal with the DRBD installation on
48
  the node or with uninitialized devices as opposed to a DRBD device.
49

50
  """
51
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
52

    
53
  _MAX_MINORS = 255
54

    
55
  @staticmethod
56
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
57
    """Returns DRBD usermode_helper currently set.
58

59
    @type filename: string
60
    @param filename: the filename to read the usermode helper from
61
    @rtype: string
62
    @return: the currently configured DRBD usermode helper
63

64
    """
65
    try:
66
      helper = utils.ReadFile(filename).splitlines()[0]
67
    except EnvironmentError, err:
68
      if err.errno == errno.ENOENT:
69
        base.ThrowError("The file %s cannot be opened, check if the module"
70
                        " is loaded (%s)", filename, str(err))
71
      else:
72
        base.ThrowError("Can't read DRBD helper file %s: %s",
73
                        filename, str(err))
74
    if not helper:
75
      base.ThrowError("Can't read any data from %s", filename)
76
    return helper
77

    
78
  @staticmethod
79
  def GetProcInfo():
80
    """Reads and parses information from /proc/drbd.
81

82
    @rtype: DRBD8Info
83
    @return: a L{DRBD8Info} instance containing the current /proc/drbd info
84

85
    """
86
    return DRBD8Info.CreateFromFile()
87

    
88
  @staticmethod
89
  def GetUsedDevs():
90
    """Compute the list of used DRBD minors.
91

92
    @rtype: list of ints
93

94
    """
95
    info = DRBD8.GetProcInfo()
96
    return filter(lambda m: not info.GetMinorStatus(m).is_unconfigured,
97
                  info.GetMinors())
98

    
99
  @staticmethod
100
  def FindUnusedMinor():
101
    """Find an unused DRBD device.
102

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

106
    @rtype: int
107

108
    """
109
    highest = None
110
    info = DRBD8.GetProcInfo()
111
    for minor in info.GetMinors():
112
      status = info.GetMinorStatus(minor)
113
      if not status.is_in_use:
114
        return minor
115
      highest = max(highest, minor)
116

    
117
    if highest is None: # there are no minors in use at all
118
      return 0
119
    if highest >= DRBD8._MAX_MINORS:
120
      logging.error("Error: no free drbd minors!")
121
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
122

    
123
    return highest + 1
124

    
125
  @staticmethod
126
  def GetCmdGenerator(info):
127
    """Creates a suitable L{BaseDRBDCmdGenerator} based on the given info.
128

129
    @type info: DRBD8Info
130
    @rtype: BaseDRBDCmdGenerator
131

132
    """
133
    version = info.GetVersion()
134
    if version["k_minor"] <= 3:
135
      return drbd_cmdgen.DRBD83CmdGenerator(version)
136
    else:
137
      return drbd_cmdgen.DRBD84CmdGenerator(version)
138

    
139
  @staticmethod
140
  def ShutdownAll(minor):
141
    """Deactivate the device.
142

143
    This will, of course, fail if the device is in use.
144

145
    @type minor: int
146
    @param minor: the minor to shut down
147

148
    """
149
    info = DRBD8.GetProcInfo()
150
    cmd_gen = DRBD8.GetCmdGenerator(info)
151

    
152
    cmd = cmd_gen.GenDownCmd(minor)
153
    result = utils.RunCmd(cmd)
154
    if result.failed:
155
      base.ThrowError("drbd%d: can't shutdown drbd device: %s",
156
                      minor, result.output)
157

    
158

    
159
class DRBD8Dev(base.BlockDev):
160
  """DRBD v8.x block device.
161

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

166
  The unique_id for the drbd device is a (local_ip, local_port,
167
  remote_ip, remote_port, local_minor, secret) tuple, and it must have
168
  two children: the data device and the meta_device. The meta device
169
  is checked for valid size and is zeroed on create.
170

171
  """
172
  _DRBD_MAJOR = 147
173

    
174
  # timeout constants
175
  _NET_RECONFIG_TIMEOUT = 60
176

    
177
  def __init__(self, unique_id, children, size, params):
178
    if children and children.count(None) > 0:
179
      children = []
180
    if len(children) not in (0, 2):
181
      raise ValueError("Invalid configuration data %s" % str(children))
182
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
183
      raise ValueError("Invalid configuration data %s" % str(unique_id))
184
    (self._lhost, self._lport,
185
     self._rhost, self._rport,
186
     self._aminor, self._secret) = unique_id
187
    if children:
188
      if not _CanReadDevice(children[1].dev_path):
189
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
190
        children = []
191
    super(DRBD8Dev, self).__init__(unique_id, children, size, params)
192
    self.major = self._DRBD_MAJOR
193

    
194
    info = DRBD8.GetProcInfo()
195
    version = info.GetVersion()
196
    if version["k_major"] != 8:
197
      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
198
                      " usage: kernel is %s.%s, ganeti wants 8.x",
199
                      version["k_major"], version["k_minor"])
200

    
201
    if version["k_minor"] <= 3:
202
      self._show_info_cls = drbd_info.DRBD83ShowInfo
203
    else:
204
      self._show_info_cls = drbd_info.DRBD84ShowInfo
205

    
206
    self._cmd_gen = DRBD8.GetCmdGenerator(info)
207

    
208
    if (self._lhost is not None and self._lhost == self._rhost and
209
            self._lport == self._rport):
210
      raise ValueError("Invalid configuration data, same local/remote %s" %
211
                       (unique_id,))
212
    self.Attach()
213

    
214
  @staticmethod
215
  def _DevPath(minor):
216
    """Return the path to a drbd device for a given minor.
217

218
    @type minor: int
219
    @rtype: string
220

221
    """
222
    return "/dev/drbd%d" % minor
223

    
224
  def _SetFromMinor(self, minor):
225
    """Set our parameters based on the given minor.
226

227
    This sets our minor variable and our dev_path.
228

229
    @type minor: int
230

231
    """
232
    if minor is None:
233
      self.minor = self.dev_path = None
234
      self.attached = False
235
    else:
236
      self.minor = minor
237
      self.dev_path = self._DevPath(minor)
238
      self.attached = True
239

    
240
  @staticmethod
241
  def _CheckMetaSize(meta_device):
242
    """Check if the given meta device looks like a valid one.
243

244
    This currently only checks the size, which must be around
245
    128MiB.
246

247
    @type meta_device: string
248
    @param meta_device: the path to the device to check
249

250
    """
251
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
252
    if result.failed:
253
      base.ThrowError("Failed to get device size: %s - %s",
254
                      result.fail_reason, result.output)
255
    try:
256
      sectors = int(result.stdout)
257
    except (TypeError, ValueError):
258
      base.ThrowError("Invalid output from blockdev: '%s'", result.stdout)
259
    num_bytes = sectors * 512
260
    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
261
      base.ThrowError("Meta device too small (%.2fMib)",
262
                      (num_bytes / 1024 / 1024))
263
    # the maximum *valid* size of the meta device when living on top
264
    # of LVM is hard to compute: it depends on the number of stripes
265
    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
266
    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
267
    # size meta device; as such, we restrict it to 1GB (a little bit
268
    # too generous, but making assumptions about PE size is hard)
269
    if num_bytes > 1024 * 1024 * 1024:
270
      base.ThrowError("Meta device too big (%.2fMiB)",
271
                      (num_bytes / 1024 / 1024))
272

    
273
  def _GetShowData(self, minor):
274
    """Return the `drbdsetup show` data.
275

276
    @type minor: int
277
    @param minor: the minor to collect show output for
278
    @rtype: string
279

280
    """
281
    result = utils.RunCmd(self._cmd_gen.GenShowCmd(minor))
282
    if result.failed:
283
      logging.error("Can't display the drbd config: %s - %s",
284
                    result.fail_reason, result.output)
285
      return None
286
    return result.stdout
287

    
288
  def _GetShowInfo(self, minor):
289
    """Return parsed information from `drbdsetup show`.
290

291
    @type minor: int
292
    @param minor: the minor to return information for
293
    @rtype: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
294

295
    """
296
    return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
297

    
298
  def _MatchesLocal(self, info):
299
    """Test if our local config matches with an existing device.
300

301
    The parameter should be as returned from `_GetShowInfo()`. This
302
    method tests if our local backing device is the same as the one in
303
    the info parameter, in effect testing if we look like the given
304
    device.
305

306
    @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
307
    @rtype: boolean
308

309
    """
310
    if self._children:
311
      backend, meta = self._children
312
    else:
313
      backend = meta = None
314

    
315
    if backend is not None:
316
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
317
    else:
318
      retval = ("local_dev" not in info)
319

    
320
    if meta is not None:
321
      retval = retval and ("meta_dev" in info and
322
                           info["meta_dev"] == meta.dev_path)
323
      if "meta_index" in info:
324
        retval = retval and info["meta_index"] == 0
325
    else:
326
      retval = retval and ("meta_dev" not in info and
327
                           "meta_index" not in info)
328
    return retval
329

    
330
  def _MatchesNet(self, info):
331
    """Test if our network config matches with an existing device.
332

333
    The parameter should be as returned from `_GetShowInfo()`. This
334
    method tests if our network configuration is the same as the one
335
    in the info parameter, in effect testing if we look like the given
336
    device.
337

338
    @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
339
    @rtype: boolean
340

341
    """
342
    if (((self._lhost is None and not ("local_addr" in info)) and
343
         (self._rhost is None and not ("remote_addr" in info)))):
344
      return True
345

    
346
    if self._lhost is None:
347
      return False
348

    
349
    if not ("local_addr" in info and
350
            "remote_addr" in info):
351
      return False
352

    
353
    retval = (info["local_addr"] == (self._lhost, self._lport))
354
    retval = (retval and
355
              info["remote_addr"] == (self._rhost, self._rport))
356
    return retval
357

    
358
  def _AssembleLocal(self, minor, backend, meta, size):
359
    """Configure the local part of a DRBD device.
360

361
    @type minor: int
362
    @param minor: the minor to assemble locally
363
    @type backend: string
364
    @param backend: path to the data device to use
365
    @type meta: string
366
    @param meta: path to the meta device to use
367
    @type size: int
368
    @param size: size in MiB
369

370
    """
371
    cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta,
372
                                          size, self.params)
373

    
374
    for cmd in cmds:
375
      result = utils.RunCmd(cmd)
376
      if result.failed:
377
        base.ThrowError("drbd%d: can't attach local disk: %s",
378
                        minor, result.output)
379

    
380
  def _AssembleNet(self, minor, net_info, protocol,
381
                   dual_pri=False, hmac=None, secret=None):
382
    """Configure the network part of the device.
383

384
    @type minor: int
385
    @param minor: the minor to assemble the network for
386
    @type net_info: (string, int, string, int)
387
    @param net_info: tuple containing the local address, local port, remote
388
      address and remote port
389
    @type protocol: string
390
    @param protocol: either "ipv4" or "ipv6"
391
    @type dual_pri: boolean
392
    @param dual_pri: whether two primaries should be allowed or not
393
    @type hmac: string
394
    @param hmac: the HMAC algorithm to use
395
    @type secret: string
396
    @param secret: the shared secret to use
397

398
    """
399
    lhost, lport, rhost, rport = net_info
400
    if None in net_info:
401
      # we don't want network connection and actually want to make
402
      # sure its shutdown
403
      self._ShutdownNet(minor)
404
      return
405

    
406
    # Workaround for a race condition. When DRBD is doing its dance to
407
    # establish a connection with its peer, it also sends the
408
    # synchronization speed over the wire. In some cases setting the
409
    # sync speed only after setting up both sides can race with DRBD
410
    # connecting, hence we set it here before telling DRBD anything
411
    # about its peer.
412
    sync_errors = self._SetMinorSyncParams(minor, self.params)
413
    if sync_errors:
414
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
415
                      (minor, utils.CommaJoin(sync_errors)))
416

    
417
    family = self._GetNetFamily(minor, lhost, rhost)
418

    
419
    cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport,
420
                                      rhost, rport, protocol,
421
                                      dual_pri, hmac, secret, self.params)
422

    
423
    result = utils.RunCmd(cmd)
424
    if result.failed:
425
      base.ThrowError("drbd%d: can't setup network: %s - %s",
426
                      minor, result.fail_reason, result.output)
427

    
428
    def _CheckNetworkConfig():
429
      info = self._GetShowInfo(minor)
430
      if not "local_addr" in info or not "remote_addr" in info:
431
        raise utils.RetryAgain()
432

    
433
      if (info["local_addr"] != (lhost, lport) or
434
          info["remote_addr"] != (rhost, rport)):
435
        raise utils.RetryAgain()
436

    
437
    try:
438
      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
439
    except utils.RetryTimeout:
440
      base.ThrowError("drbd%d: timeout while configuring network", minor)
441

    
442
  @staticmethod
443
  def _GetNetFamily(minor, lhost, rhost):
444
    if netutils.IP6Address.IsValid(lhost):
445
      if not netutils.IP6Address.IsValid(rhost):
446
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
447
                        (minor, lhost, rhost))
448
      return "ipv6"
449
    elif netutils.IP4Address.IsValid(lhost):
450
      if not netutils.IP4Address.IsValid(rhost):
451
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
452
                        (minor, lhost, rhost))
453
      return "ipv4"
454
    else:
455
      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
456

    
457
  def AddChildren(self, devices):
458
    """Add a disk to the DRBD device.
459

460
    @type devices: list of L{BlockDev}
461
    @param devices: a list of exactly two L{BlockDev} objects; the first
462
      denotes the data device, the second the meta device for this DRBD device
463

464
    """
465
    if self.minor is None:
466
      base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
467
                      self._aminor)
468
    if len(devices) != 2:
469
      base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
470
    info = self._GetShowInfo(self.minor)
471
    if "local_dev" in info:
472
      base.ThrowError("drbd%d: already attached to a local disk", self.minor)
473
    backend, meta = devices
474
    if backend.dev_path is None or meta.dev_path is None:
475
      base.ThrowError("drbd%d: children not ready during AddChildren",
476
                      self.minor)
477
    backend.Open()
478
    meta.Open()
479
    self._CheckMetaSize(meta.dev_path)
480
    self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path)
481

    
482
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
483
    self._children = devices
484

    
485
  def RemoveChildren(self, devices):
486
    """Detach the drbd device from local storage.
487

488
    @type devices: list of L{BlockDev}
489
    @param devices: a list of exactly two L{BlockDev} objects; the first
490
      denotes the data device, the second the meta device for this DRBD device
491

492
    """
493
    if self.minor is None:
494
      base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
495
                      self._aminor)
496
    # early return if we don't actually have backing storage
497
    info = self._GetShowInfo(self.minor)
498
    if "local_dev" not in info:
499
      return
500
    if len(self._children) != 2:
501
      base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
502
                      self._children)
503
    if self._children.count(None) == 2: # we don't actually have children :)
504
      logging.warning("drbd%d: requested detach while detached", self.minor)
505
      return
506
    if len(devices) != 2:
507
      base.ThrowError("drbd%d: we need two children in RemoveChildren",
508
                      self.minor)
509
    for child, dev in zip(self._children, devices):
510
      if dev != child.dev_path:
511
        base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
512
                        " RemoveChildren", self.minor, dev, child.dev_path)
513

    
514
    self._ShutdownLocal(self.minor)
515
    self._children = []
516

    
517
  def _SetMinorSyncParams(self, minor, params):
518
    """Set the parameters of the DRBD syncer.
519

520
    This is the low-level implementation.
521

522
    @type minor: int
523
    @param minor: the drbd minor whose settings we change
524
    @type params: dict
525
    @param params: LD level disk parameters related to the synchronization
526
    @rtype: list
527
    @return: a list of error messages
528

529
    """
530
    cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
531
    result = utils.RunCmd(cmd)
532
    if result.failed:
533
      msg = ("Can't change syncer rate: %s - %s" %
534
             (result.fail_reason, result.output))
535
      logging.error(msg)
536
      return [msg]
537

    
538
    return []
539

    
540
  def SetSyncParams(self, params):
541
    """Set the synchronization parameters of the DRBD syncer.
542

543
    See L{BlockDev.SetSyncParams} for parameter description.
544

545
    """
546
    if self.minor is None:
547
      err = "Not attached during SetSyncParams"
548
      logging.info(err)
549
      return [err]
550

    
551
    children_result = super(DRBD8Dev, self).SetSyncParams(params)
552
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
553
    return children_result
554

    
555
  def PauseResumeSync(self, pause):
556
    """Pauses or resumes the sync of a DRBD device.
557

558
    See L{BlockDev.PauseResumeSync} for parameter description.
559

560
    """
561
    if self.minor is None:
562
      logging.info("Not attached during PauseSync")
563
      return False
564

    
565
    children_result = super(DRBD8Dev, self).PauseResumeSync(pause)
566

    
567
    if pause:
568
      cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
569
    else:
570
      cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
571

    
572
    result = utils.RunCmd(cmd)
573
    if result.failed:
574
      logging.error("Can't %s: %s - %s", cmd,
575
                    result.fail_reason, result.output)
576
    return not result.failed and children_result
577

    
578
  def GetProcStatus(self):
579
    """Return the current status data from /proc/drbd for this device.
580

581
    @rtype: DRBD8Status
582

583
    """
584
    if self.minor is None:
585
      base.ThrowError("drbd%d: GetStats() called while not attached",
586
                      self._aminor)
587
    info = DRBD8.GetProcInfo()
588
    if not info.HasMinorStatus(self.minor):
589
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
590
    return info.GetMinorStatus(self.minor)
591

    
592
  def GetSyncStatus(self):
593
    """Returns the sync status of the device.
594

595
    If sync_percent is None, it means all is ok
596
    If estimated_time is None, it means we can't estimate
597
    the time needed, otherwise it's the time left in seconds.
598

599
    We set the is_degraded parameter to True on two conditions:
600
    network not connected or local disk missing.
601

602
    We compute the ldisk parameter based on whether we have a local
603
    disk or not.
604

605
    @rtype: objects.BlockDevStatus
606

607
    """
608
    if self.minor is None and not self.Attach():
609
      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
610

    
611
    stats = self.GetProcStatus()
612
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
613

    
614
    if stats.is_disk_uptodate:
615
      ldisk_status = constants.LDS_OKAY
616
    elif stats.is_diskless:
617
      ldisk_status = constants.LDS_FAULTY
618
    else:
619
      ldisk_status = constants.LDS_UNKNOWN
620

    
621
    return objects.BlockDevStatus(dev_path=self.dev_path,
622
                                  major=self.major,
623
                                  minor=self.minor,
624
                                  sync_percent=stats.sync_percent,
625
                                  estimated_time=stats.est_time,
626
                                  is_degraded=is_degraded,
627
                                  ldisk_status=ldisk_status)
628

    
629
  def Open(self, force=False):
630
    """Make the local state primary.
631

632
    If the 'force' parameter is given, DRBD is instructed to switch the device
633
    into primary mode. Since this is a potentially dangerous operation, the
634
    force flag should be only given after creation, when it actually is
635
    mandatory.
636

637
    """
638
    if self.minor is None and not self.Attach():
639
      logging.error("DRBD cannot attach to a device during open")
640
      return False
641

    
642
    cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
643

    
644
    result = utils.RunCmd(cmd)
645
    if result.failed:
646
      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
647
                      result.output)
648

    
649
  def Close(self):
650
    """Make the local state secondary.
651

652
    This will, of course, fail if the device is in use.
653

654
    """
655
    if self.minor is None and not self.Attach():
656
      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
657
    cmd = self._cmd_gen.GenSecondaryCmd(self.minor)
658
    result = utils.RunCmd(cmd)
659
    if result.failed:
660
      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
661
                      self.minor, result.output)
662

    
663
  def DisconnectNet(self):
664
    """Removes network configuration.
665

666
    This method shutdowns the network side of the device.
667

668
    The method will wait up to a hardcoded timeout for the device to
669
    go into standalone after the 'disconnect' command before
670
    re-configuring it, as sometimes it takes a while for the
671
    disconnect to actually propagate and thus we might issue a 'net'
672
    command while the device is still connected. If the device will
673
    still be attached to the network and we time out, we raise an
674
    exception.
675

676
    """
677
    if self.minor is None:
678
      base.ThrowError("drbd%d: disk not attached in re-attach net",
679
                      self._aminor)
680

    
681
    if None in (self._lhost, self._lport, self._rhost, self._rport):
682
      base.ThrowError("drbd%d: DRBD disk missing network info in"
683
                      " DisconnectNet()", self.minor)
684

    
685
    class _DisconnectStatus:
686
      def __init__(self, ever_disconnected):
687
        self.ever_disconnected = ever_disconnected
688

    
689
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
690

    
691
    def _WaitForDisconnect():
692
      if self.GetProcStatus().is_standalone:
693
        return
694

    
695
      # retry the disconnect, it seems possible that due to a well-time
696
      # disconnect on the peer, my disconnect command might be ignored and
697
      # forgotten
698
      dstatus.ever_disconnected = \
699
        base.IgnoreError(self._ShutdownNet, self.minor) or \
700
        dstatus.ever_disconnected
701

    
702
      raise utils.RetryAgain()
703

    
704
    # Keep start time
705
    start_time = time.time()
706

    
707
    try:
708
      # Start delay at 100 milliseconds and grow up to 2 seconds
709
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
710
                  self._NET_RECONFIG_TIMEOUT)
711
    except utils.RetryTimeout:
712
      if dstatus.ever_disconnected:
713
        msg = ("drbd%d: device did not react to the"
714
               " 'disconnect' command in a timely manner")
715
      else:
716
        msg = "drbd%d: can't shutdown network, even after multiple retries"
717

    
718
      base.ThrowError(msg, self.minor)
719

    
720
    reconfig_time = time.time() - start_time
721
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
722
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
723
                   self.minor, reconfig_time)
724

    
725
  def AttachNet(self, multimaster):
726
    """Reconnects the network.
727

728
    This method connects the network side of the device with a
729
    specified multi-master flag. The device needs to be 'Standalone'
730
    but have valid network configuration data.
731

732
    @type multimaster: boolean
733
    @param multimaster: init the network in dual-primary mode
734

735
    """
736
    if self.minor is None:
737
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
738

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

    
742
    status = self.GetProcStatus()
743

    
744
    if not status.is_standalone:
745
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
746
                      self.minor)
747

    
748
    self._AssembleNet(self.minor,
749
                      (self._lhost, self._lport, self._rhost, self._rport),
750
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
751
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
752

    
753
  def Attach(self):
754
    """Check if our minor is configured.
755

756
    This doesn't do any device configurations - it only checks if the
757
    minor is in a state different from Unconfigured.
758

759
    Note that this function will not change the state of the system in
760
    any way (except in case of side-effects caused by reading from
761
    /proc).
762

763
    """
764
    used_devs = DRBD8.GetUsedDevs()
765
    if self._aminor in used_devs:
766
      minor = self._aminor
767
    else:
768
      minor = None
769

    
770
    self._SetFromMinor(minor)
771
    return minor is not None
772

    
773
  def Assemble(self):
774
    """Assemble the drbd.
775

776
    Method:
777
      - if we have a configured device, we try to ensure that it matches
778
        our config
779
      - if not, we create it from zero
780
      - anyway, set the device parameters
781

782
    """
783
    super(DRBD8Dev, self).Assemble()
784

    
785
    self.Attach()
786
    if self.minor is None:
787
      # local device completely unconfigured
788
      self._FastAssemble()
789
    else:
790
      # we have to recheck the local and network status and try to fix
791
      # the device
792
      self._SlowAssemble()
793

    
794
    sync_errors = self.SetSyncParams(self.params)
795
    if sync_errors:
796
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
797
                      (self.minor, utils.CommaJoin(sync_errors)))
798

    
799
  def _SlowAssemble(self):
800
    """Assembles the DRBD device from a (partially) configured device.
801

802
    In case of partially attached (local device matches but no network
803
    setup), we perform the network attach. If successful, we re-test
804
    the attach if can return success.
805

806
    """
807
    # TODO: Rewrite to not use a for loop just because there is 'break'
808
    # pylint: disable=W0631
809
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
810
    for minor in (self._aminor,):
811
      info = self._GetShowInfo(minor)
812
      match_l = self._MatchesLocal(info)
813
      match_r = self._MatchesNet(info)
814

    
815
      if match_l and match_r:
816
        # everything matches
817
        break
818

    
819
      if match_l and not match_r and "local_addr" not in info:
820
        # disk matches, but not attached to network, attach and recheck
821
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
822
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
823
        if self._MatchesNet(self._GetShowInfo(minor)):
824
          break
825
        else:
826
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
827
                          " show' disagrees", minor)
828

    
829
      if match_r and "local_dev" not in info:
830
        # no local disk, but network attached and it matches
831
        self._AssembleLocal(minor, self._children[0].dev_path,
832
                            self._children[1].dev_path, self.size)
833
        if self._MatchesLocal(self._GetShowInfo(minor)):
834
          break
835
        else:
836
          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
837
                          " show' disagrees", minor)
838

    
839
      # this case must be considered only if we actually have local
840
      # storage, i.e. not in diskless mode, because all diskless
841
      # devices are equal from the point of view of local
842
      # configuration
843
      if (match_l and "local_dev" in info and
844
          not match_r and "local_addr" in info):
845
        # strange case - the device network part points to somewhere
846
        # else, even though its local storage is ours; as we own the
847
        # drbd space, we try to disconnect from the remote peer and
848
        # reconnect to our correct one
849
        try:
850
          self._ShutdownNet(minor)
851
        except errors.BlockDeviceError, err:
852
          base.ThrowError("drbd%d: device has correct local storage, wrong"
853
                          " remote peer and is unable to disconnect in order"
854
                          " to attach to the correct peer: %s", minor, str(err))
855
        # note: _AssembleNet also handles the case when we don't want
856
        # local storage (i.e. one or more of the _[lr](host|port) is
857
        # None)
858
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
859
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
860
        if self._MatchesNet(self._GetShowInfo(minor)):
861
          break
862
        else:
863
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
864
                          " show' disagrees", minor)
865

    
866
    else:
867
      minor = None
868

    
869
    self._SetFromMinor(minor)
870
    if minor is None:
871
      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
872
                      self._aminor)
873

    
874
  def _FastAssemble(self):
875
    """Assemble the drbd device from zero.
876

877
    This is run when in Assemble we detect our minor is unused.
878

879
    """
880
    minor = self._aminor
881
    if self._children and self._children[0] and self._children[1]:
882
      self._AssembleLocal(minor, self._children[0].dev_path,
883
                          self._children[1].dev_path, self.size)
884
    if self._lhost and self._lport and self._rhost and self._rport:
885
      self._AssembleNet(minor,
886
                        (self._lhost, self._lport, self._rhost, self._rport),
887
                        constants.DRBD_NET_PROTOCOL,
888
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
889
    self._SetFromMinor(minor)
890

    
891
  def _ShutdownLocal(self, minor):
892
    """Detach from the local device.
893

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

897
    @type minor: int
898
    @param minor: the device to detach from the local device
899

900
    """
901
    cmd = self._cmd_gen.GenDetachCmd(minor)
902
    result = utils.RunCmd(cmd)
903
    if result.failed:
904
      base.ThrowError("drbd%d: can't detach local disk: %s",
905
                      minor, result.output)
906

    
907
  def _ShutdownNet(self, minor):
908
    """Disconnect from the remote peer.
909

910
    This fails if we don't have a local device.
911

912
    @type minor: boolean
913
    @param minor: the device to disconnect from the remote peer
914

915
    """
916
    family = self._GetNetFamily(minor, self._lhost, self._rhost)
917
    cmd = self._cmd_gen.GenDisconnectCmd(minor, family,
918
                                         self._lhost, self._lport,
919
                                         self._rhost, self._rport)
920
    result = utils.RunCmd(cmd)
921
    if result.failed:
922
      base.ThrowError("drbd%d: can't shutdown network: %s",
923
                      minor, result.output)
924

    
925
  def Shutdown(self):
926
    """Shutdown the DRBD device.
927

928
    """
929
    if self.minor is None and not self.Attach():
930
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
931
      return
932
    minor = self.minor
933
    self.minor = None
934
    self.dev_path = None
935
    DRBD8.ShutdownAll(minor)
936

    
937
  def Remove(self):
938
    """Stub remove for DRBD devices.
939

940
    """
941
    self.Shutdown()
942

    
943
  def Rename(self, new_id):
944
    """Rename a device.
945

946
    This is not supported for drbd devices.
947

948
    """
949
    raise errors.ProgrammerError("Can't rename a drbd device")
950

    
951
  def Grow(self, amount, dryrun, backingstore):
952
    """Resize the DRBD device and its backing storage.
953

954
    See L{BlockDev.Grow} for parameter description.
955

956
    """
957
    if self.minor is None:
958
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
959
    if len(self._children) != 2 or None in self._children:
960
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
961
    self._children[0].Grow(amount, dryrun, backingstore)
962
    if dryrun or backingstore:
963
      # DRBD does not support dry-run mode and is not backing storage,
964
      # so we'll return here
965
      return
966
    cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
967
    result = utils.RunCmd(cmd)
968
    if result.failed:
969
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
970

    
971
  @classmethod
972
  def _InitMeta(cls, minor, dev_path):
973
    """Initialize a meta device.
974

975
    This will not work if the given minor is in use.
976

977
    @type minor: int
978
    @param minor: the DRBD minor whose (future) meta device should be
979
      initialized
980
    @type dev_path: string
981
    @param dev_path: path to the meta device to initialize
982

983
    """
984
    # Zero the metadata first, in order to make sure drbdmeta doesn't
985
    # try to auto-detect existing filesystems or similar (see
986
    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
987
    # care about the first 128MB of data in the device, even though it
988
    # can be bigger
989
    result = utils.RunCmd([constants.DD_CMD,
990
                           "if=/dev/zero", "of=%s" % dev_path,
991
                           "bs=1048576", "count=128", "oflag=direct"])
992
    if result.failed:
993
      base.ThrowError("Can't wipe the meta device: %s", result.output)
994

    
995
    info = DRBD8.GetProcInfo()
996
    cmd_gen = DRBD8.GetCmdGenerator(info)
997
    cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
998

    
999
    result = utils.RunCmd(cmd)
1000
    if result.failed:
1001
      base.ThrowError("Can't initialize meta device: %s", result.output)
1002

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

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

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

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

    
1037

    
1038
def _CanReadDevice(path):
1039
  """Check if we can read from the given device.
1040

1041
  This tries to read the first 128k of the device.
1042

1043
  @type path: string
1044

1045
  """
1046
  try:
1047
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1048
    return True
1049
  except EnvironmentError:
1050
    logging.warning("Can't read from device %s", path, exc_info=True)
1051
    return False