Statistics
| Branch: | Tag: | Revision:

root / lib / storage / drbd.py @ ba174485

History | View | Annotate | Download (35.4 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 (pnode_uuid, snode_uuid,
167
  port, pnode_minor, lnode_minor, secret) tuple, and it must have
168
  two children: the data device and the meta_device. The meta
169
  device 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, dyn_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
    if constants.DDP_LOCAL_IP not in dyn_params or \
185
       constants.DDP_REMOTE_IP not in dyn_params or \
186
       constants.DDP_LOCAL_MINOR not in dyn_params or \
187
       constants.DDP_REMOTE_MINOR not in dyn_params:
188
      raise ValueError("Invalid dynamic parameters %s" % str(dyn_params))
189

    
190
    self._lhost = dyn_params[constants.DDP_LOCAL_IP]
191
    self._lport = unique_id[2]
192
    self._rhost = dyn_params[constants.DDP_REMOTE_IP]
193
    self._rport = unique_id[2]
194
    self._aminor = dyn_params[constants.DDP_LOCAL_MINOR]
195
    self._secret = unique_id[5]
196

    
197
    if children:
198
      if not _CanReadDevice(children[1].dev_path):
199
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
200
        children = []
201
    super(DRBD8Dev, self).__init__(unique_id, children, size, params,
202
                                   dyn_params)
203
    self.major = self._DRBD_MAJOR
204

    
205
    info = DRBD8.GetProcInfo()
206
    version = info.GetVersion()
207
    if version["k_major"] != 8:
208
      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
209
                      " usage: kernel is %s.%s, ganeti wants 8.x",
210
                      version["k_major"], version["k_minor"])
211

    
212
    if version["k_minor"] <= 3:
213
      self._show_info_cls = drbd_info.DRBD83ShowInfo
214
    else:
215
      self._show_info_cls = drbd_info.DRBD84ShowInfo
216

    
217
    self._cmd_gen = DRBD8.GetCmdGenerator(info)
218

    
219
    if (self._lhost is not None and self._lhost == self._rhost and
220
            self._lport == self._rport):
221
      raise ValueError("Invalid configuration data, same local/remote %s, %s" %
222
                       (unique_id, dyn_params))
223
    self.Attach()
224

    
225
  @staticmethod
226
  def _DevPath(minor):
227
    """Return the path to a drbd device for a given minor.
228

229
    @type minor: int
230
    @rtype: string
231

232
    """
233
    return "/dev/drbd%d" % minor
234

    
235
  def _SetFromMinor(self, minor):
236
    """Set our parameters based on the given minor.
237

238
    This sets our minor variable and our dev_path.
239

240
    @type minor: int
241

242
    """
243
    if minor is None:
244
      self.minor = self.dev_path = None
245
      self.attached = False
246
    else:
247
      self.minor = minor
248
      self.dev_path = self._DevPath(minor)
249
      self.attached = True
250

    
251
  @staticmethod
252
  def _CheckMetaSize(meta_device):
253
    """Check if the given meta device looks like a valid one.
254

255
    This currently only checks the size, which must be around
256
    128MiB.
257

258
    @type meta_device: string
259
    @param meta_device: the path to the device to check
260

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

    
284
  def _GetShowData(self, minor):
285
    """Return the `drbdsetup show` data.
286

287
    @type minor: int
288
    @param minor: the minor to collect show output for
289
    @rtype: string
290

291
    """
292
    result = utils.RunCmd(self._cmd_gen.GenShowCmd(minor))
293
    if result.failed:
294
      logging.error("Can't display the drbd config: %s - %s",
295
                    result.fail_reason, result.output)
296
      return None
297
    return result.stdout
298

    
299
  def _GetShowInfo(self, minor):
300
    """Return parsed information from `drbdsetup show`.
301

302
    @type minor: int
303
    @param minor: the minor to return information for
304
    @rtype: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
305

306
    """
307
    return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
308

    
309
  def _MatchesLocal(self, info):
310
    """Test if our local config matches with an existing device.
311

312
    The parameter should be as returned from `_GetShowInfo()`. This
313
    method tests if our local backing device is the same as the one in
314
    the info parameter, in effect testing if we look like the given
315
    device.
316

317
    @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
318
    @rtype: boolean
319

320
    """
321
    if self._children:
322
      backend, meta = self._children
323
    else:
324
      backend = meta = None
325

    
326
    if backend is not None:
327
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
328
    else:
329
      retval = ("local_dev" not in info)
330

    
331
    if meta is not None:
332
      retval = retval and ("meta_dev" in info and
333
                           info["meta_dev"] == meta.dev_path)
334
      if "meta_index" in info:
335
        retval = retval and info["meta_index"] == 0
336
    else:
337
      retval = retval and ("meta_dev" not in info and
338
                           "meta_index" not in info)
339
    return retval
340

    
341
  def _MatchesNet(self, info):
342
    """Test if our network config matches with an existing device.
343

344
    The parameter should be as returned from `_GetShowInfo()`. This
345
    method tests if our network configuration is the same as the one
346
    in the info parameter, in effect testing if we look like the given
347
    device.
348

349
    @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
350
    @rtype: boolean
351

352
    """
353
    if (((self._lhost is None and not ("local_addr" in info)) and
354
         (self._rhost is None and not ("remote_addr" in info)))):
355
      return True
356

    
357
    if self._lhost is None:
358
      return False
359

    
360
    if not ("local_addr" in info and
361
            "remote_addr" in info):
362
      return False
363

    
364
    retval = (info["local_addr"] == (self._lhost, self._lport))
365
    retval = (retval and
366
              info["remote_addr"] == (self._rhost, self._rport))
367
    return retval
368

    
369
  def _AssembleLocal(self, minor, backend, meta, size):
370
    """Configure the local part of a DRBD device.
371

372
    @type minor: int
373
    @param minor: the minor to assemble locally
374
    @type backend: string
375
    @param backend: path to the data device to use
376
    @type meta: string
377
    @param meta: path to the meta device to use
378
    @type size: int
379
    @param size: size in MiB
380

381
    """
382
    cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta,
383
                                          size, self.params)
384

    
385
    for cmd in cmds:
386
      result = utils.RunCmd(cmd)
387
      if result.failed:
388
        base.ThrowError("drbd%d: can't attach local disk: %s",
389
                        minor, result.output)
390

    
391
  def _AssembleNet(self, minor, net_info, dual_pri=False, hmac=None,
392
                   secret=None):
393
    """Configure the network part of the device.
394

395
    @type minor: int
396
    @param minor: the minor to assemble the network for
397
    @type net_info: (string, int, string, int)
398
    @param net_info: tuple containing the local address, local port, remote
399
      address and remote port
400
    @type dual_pri: boolean
401
    @param dual_pri: whether two primaries should be allowed or not
402
    @type hmac: string
403
    @param hmac: the HMAC algorithm to use
404
    @type secret: string
405
    @param secret: the shared secret to use
406

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

    
415
    if dual_pri:
416
      protocol = constants.DRBD_MIGRATION_NET_PROTOCOL
417
    else:
418
      protocol = self.params[constants.LDP_PROTOCOL]
419

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

    
431
    family = self._GetNetFamily(minor, lhost, rhost)
432

    
433
    cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport,
434
                                      rhost, rport, protocol,
435
                                      dual_pri, hmac, secret, self.params)
436

    
437
    result = utils.RunCmd(cmd)
438
    if result.failed:
439
      base.ThrowError("drbd%d: can't setup network: %s - %s",
440
                      minor, result.fail_reason, result.output)
441

    
442
    def _CheckNetworkConfig():
443
      info = self._GetShowInfo(minor)
444
      if not "local_addr" in info or not "remote_addr" in info:
445
        raise utils.RetryAgain()
446

    
447
      if (info["local_addr"] != (lhost, lport) or
448
          info["remote_addr"] != (rhost, rport)):
449
        raise utils.RetryAgain()
450

    
451
    try:
452
      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
453
    except utils.RetryTimeout:
454
      base.ThrowError("drbd%d: timeout while configuring network", minor)
455

    
456
  @staticmethod
457
  def _GetNetFamily(minor, lhost, rhost):
458
    if netutils.IP6Address.IsValid(lhost):
459
      if not netutils.IP6Address.IsValid(rhost):
460
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
461
                        (minor, lhost, rhost))
462
      return "ipv6"
463
    elif netutils.IP4Address.IsValid(lhost):
464
      if not netutils.IP4Address.IsValid(rhost):
465
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
466
                        (minor, lhost, rhost))
467
      return "ipv4"
468
    else:
469
      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
470

    
471
  def AddChildren(self, devices):
472
    """Add a disk to the DRBD device.
473

474
    @type devices: list of L{BlockDev}
475
    @param devices: a list of exactly two L{BlockDev} objects; the first
476
      denotes the data device, the second the meta device for this DRBD device
477

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

    
496
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
497
    self._children = devices
498

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

502
    @type devices: list of L{BlockDev}
503
    @param devices: a list of exactly two L{BlockDev} objects; the first
504
      denotes the data device, the second the meta device for this DRBD device
505

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

    
528
    self._ShutdownLocal(self.minor)
529
    self._children = []
530

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

534
    This is the low-level implementation.
535

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

543
    """
544
    cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
545
    result = utils.RunCmd(cmd)
546
    if result.failed:
547
      msg = ("Can't change syncer rate: %s - %s" %
548
             (result.fail_reason, result.output))
549
      logging.error(msg)
550
      return [msg]
551

    
552
    return []
553

    
554
  def SetSyncParams(self, params):
555
    """Set the synchronization parameters of the DRBD syncer.
556

557
    See L{BlockDev.SetSyncParams} for parameter description.
558

559
    """
560
    if self.minor is None:
561
      err = "Not attached during SetSyncParams"
562
      logging.info(err)
563
      return [err]
564

    
565
    children_result = super(DRBD8Dev, self).SetSyncParams(params)
566
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
567
    return children_result
568

    
569
  def PauseResumeSync(self, pause):
570
    """Pauses or resumes the sync of a DRBD device.
571

572
    See L{BlockDev.PauseResumeSync} for parameter description.
573

574
    """
575
    if self.minor is None:
576
      logging.info("Not attached during PauseSync")
577
      return False
578

    
579
    children_result = super(DRBD8Dev, self).PauseResumeSync(pause)
580

    
581
    if pause:
582
      cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
583
    else:
584
      cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
585

    
586
    result = utils.RunCmd(cmd)
587
    if result.failed:
588
      logging.error("Can't %s: %s - %s", cmd,
589
                    result.fail_reason, result.output)
590
    return not result.failed and children_result
591

    
592
  def GetProcStatus(self):
593
    """Return the current status data from /proc/drbd for this device.
594

595
    @rtype: DRBD8Status
596

597
    """
598
    if self.minor is None:
599
      base.ThrowError("drbd%d: GetStats() called while not attached",
600
                      self._aminor)
601
    info = DRBD8.GetProcInfo()
602
    if not info.HasMinorStatus(self.minor):
603
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
604
    return info.GetMinorStatus(self.minor)
605

    
606
  def GetSyncStatus(self):
607
    """Returns the sync status of the device.
608

609
    If sync_percent is None, it means all is ok
610
    If estimated_time is None, it means we can't estimate
611
    the time needed, otherwise it's the time left in seconds.
612

613
    We set the is_degraded parameter to True on two conditions:
614
    network not connected or local disk missing.
615

616
    We compute the ldisk parameter based on whether we have a local
617
    disk or not.
618

619
    @rtype: objects.BlockDevStatus
620

621
    """
622
    if self.minor is None and not self.Attach():
623
      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
624

    
625
    stats = self.GetProcStatus()
626
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
627

    
628
    if stats.is_disk_uptodate:
629
      ldisk_status = constants.LDS_OKAY
630
    elif stats.is_diskless:
631
      ldisk_status = constants.LDS_FAULTY
632
    else:
633
      ldisk_status = constants.LDS_UNKNOWN
634

    
635
    return objects.BlockDevStatus(dev_path=self.dev_path,
636
                                  major=self.major,
637
                                  minor=self.minor,
638
                                  sync_percent=stats.sync_percent,
639
                                  estimated_time=stats.est_time,
640
                                  is_degraded=is_degraded,
641
                                  ldisk_status=ldisk_status)
642

    
643
  def Open(self, force=False):
644
    """Make the local state primary.
645

646
    If the 'force' parameter is given, DRBD is instructed to switch the device
647
    into primary mode. Since this is a potentially dangerous operation, the
648
    force flag should be only given after creation, when it actually is
649
    mandatory.
650

651
    """
652
    if self.minor is None and not self.Attach():
653
      logging.error("DRBD cannot attach to a device during open")
654
      return False
655

    
656
    cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
657

    
658
    result = utils.RunCmd(cmd)
659
    if result.failed:
660
      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
661
                      result.output)
662

    
663
  def Close(self):
664
    """Make the local state secondary.
665

666
    This will, of course, fail if the device is in use.
667

668
    """
669
    if self.minor is None and not self.Attach():
670
      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
671
    cmd = self._cmd_gen.GenSecondaryCmd(self.minor)
672
    result = utils.RunCmd(cmd)
673
    if result.failed:
674
      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
675
                      self.minor, result.output)
676

    
677
  def DisconnectNet(self):
678
    """Removes network configuration.
679

680
    This method shutdowns the network side of the device.
681

682
    The method will wait up to a hardcoded timeout for the device to
683
    go into standalone after the 'disconnect' command before
684
    re-configuring it, as sometimes it takes a while for the
685
    disconnect to actually propagate and thus we might issue a 'net'
686
    command while the device is still connected. If the device will
687
    still be attached to the network and we time out, we raise an
688
    exception.
689

690
    """
691
    if self.minor is None:
692
      base.ThrowError("drbd%d: disk not attached in re-attach net",
693
                      self._aminor)
694

    
695
    if None in (self._lhost, self._lport, self._rhost, self._rport):
696
      base.ThrowError("drbd%d: DRBD disk missing network info in"
697
                      " DisconnectNet()", self.minor)
698

    
699
    class _DisconnectStatus:
700
      def __init__(self, ever_disconnected):
701
        self.ever_disconnected = ever_disconnected
702

    
703
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
704

    
705
    def _WaitForDisconnect():
706
      if self.GetProcStatus().is_standalone:
707
        return
708

    
709
      # retry the disconnect, it seems possible that due to a well-time
710
      # disconnect on the peer, my disconnect command might be ignored and
711
      # forgotten
712
      dstatus.ever_disconnected = \
713
        base.IgnoreError(self._ShutdownNet, self.minor) or \
714
        dstatus.ever_disconnected
715

    
716
      raise utils.RetryAgain()
717

    
718
    # Keep start time
719
    start_time = time.time()
720

    
721
    try:
722
      # Start delay at 100 milliseconds and grow up to 2 seconds
723
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
724
                  self._NET_RECONFIG_TIMEOUT)
725
    except utils.RetryTimeout:
726
      if dstatus.ever_disconnected:
727
        msg = ("drbd%d: device did not react to the"
728
               " 'disconnect' command in a timely manner")
729
      else:
730
        msg = "drbd%d: can't shutdown network, even after multiple retries"
731

    
732
      base.ThrowError(msg, self.minor)
733

    
734
    reconfig_time = time.time() - start_time
735
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
736
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
737
                   self.minor, reconfig_time)
738

    
739
  def AttachNet(self, multimaster):
740
    """Reconnects the network.
741

742
    This method connects the network side of the device with a
743
    specified multi-master flag. The device needs to be 'Standalone'
744
    but have valid network configuration data.
745

746
    @type multimaster: boolean
747
    @param multimaster: init the network in dual-primary mode
748

749
    """
750
    if self.minor is None:
751
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
752

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

    
756
    status = self.GetProcStatus()
757

    
758
    if not status.is_standalone:
759
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
760
                      self.minor)
761

    
762
    self._AssembleNet(self.minor,
763
                      (self._lhost, self._lport, self._rhost, self._rport),
764
                      dual_pri=multimaster, hmac=constants.DRBD_HMAC_ALG,
765
                      secret=self._secret)
766

    
767
  def Attach(self):
768
    """Check if our minor is configured.
769

770
    This doesn't do any device configurations - it only checks if the
771
    minor is in a state different from Unconfigured.
772

773
    Note that this function will not change the state of the system in
774
    any way (except in case of side-effects caused by reading from
775
    /proc).
776

777
    """
778
    used_devs = DRBD8.GetUsedDevs()
779
    if self._aminor in used_devs:
780
      minor = self._aminor
781
    else:
782
      minor = None
783

    
784
    self._SetFromMinor(minor)
785
    return minor is not None
786

    
787
  def Assemble(self):
788
    """Assemble the drbd.
789

790
    Method:
791
      - if we have a configured device, we try to ensure that it matches
792
        our config
793
      - if not, we create it from zero
794
      - anyway, set the device parameters
795

796
    """
797
    super(DRBD8Dev, self).Assemble()
798

    
799
    self.Attach()
800
    if self.minor is None:
801
      # local device completely unconfigured
802
      self._FastAssemble()
803
    else:
804
      # we have to recheck the local and network status and try to fix
805
      # the device
806
      self._SlowAssemble()
807

    
808
    sync_errors = self.SetSyncParams(self.params)
809
    if sync_errors:
810
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
811
                      (self.minor, utils.CommaJoin(sync_errors)))
812

    
813
  def _SlowAssemble(self):
814
    """Assembles the DRBD device from a (partially) configured device.
815

816
    In case of partially attached (local device matches but no network
817
    setup), we perform the network attach. If successful, we re-test
818
    the attach if can return success.
819

820
    """
821
    # TODO: Rewrite to not use a for loop just because there is 'break'
822
    # pylint: disable=W0631
823
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
824
    for minor in (self._aminor,):
825
      info = self._GetShowInfo(minor)
826
      match_l = self._MatchesLocal(info)
827
      match_r = self._MatchesNet(info)
828

    
829
      if match_l and match_r:
830
        # everything matches
831
        break
832

    
833
      if match_l and not match_r and "local_addr" not in info:
834
        # disk matches, but not attached to network, attach and recheck
835
        self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG,
836
                          secret=self._secret)
837
        if self._MatchesNet(self._GetShowInfo(minor)):
838
          break
839
        else:
840
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
841
                          " show' disagrees", minor)
842

    
843
      if match_r and "local_dev" not in info:
844
        # no local disk, but network attached and it matches
845
        self._AssembleLocal(minor, self._children[0].dev_path,
846
                            self._children[1].dev_path, self.size)
847
        if self._MatchesLocal(self._GetShowInfo(minor)):
848
          break
849
        else:
850
          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
851
                          " show' disagrees", minor)
852

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

    
880
    else:
881
      minor = None
882

    
883
    self._SetFromMinor(minor)
884
    if minor is None:
885
      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
886
                      self._aminor)
887

    
888
  def _FastAssemble(self):
889
    """Assemble the drbd device from zero.
890

891
    This is run when in Assemble we detect our minor is unused.
892

893
    """
894
    minor = self._aminor
895
    if self._children and self._children[0] and self._children[1]:
896
      self._AssembleLocal(minor, self._children[0].dev_path,
897
                          self._children[1].dev_path, self.size)
898
    if self._lhost and self._lport and self._rhost and self._rport:
899
      self._AssembleNet(minor,
900
                        (self._lhost, self._lport, self._rhost, self._rport),
901
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
902
    self._SetFromMinor(minor)
903

    
904
  def _ShutdownLocal(self, minor):
905
    """Detach from the local device.
906

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

910
    @type minor: int
911
    @param minor: the device to detach from the local device
912

913
    """
914
    cmd = self._cmd_gen.GenDetachCmd(minor)
915
    result = utils.RunCmd(cmd)
916
    if result.failed:
917
      base.ThrowError("drbd%d: can't detach local disk: %s",
918
                      minor, result.output)
919

    
920
  def _ShutdownNet(self, minor):
921
    """Disconnect from the remote peer.
922

923
    This fails if we don't have a local device.
924

925
    @type minor: boolean
926
    @param minor: the device to disconnect from the remote peer
927

928
    """
929
    family = self._GetNetFamily(minor, self._lhost, self._rhost)
930
    cmd = self._cmd_gen.GenDisconnectCmd(minor, family,
931
                                         self._lhost, self._lport,
932
                                         self._rhost, self._rport)
933
    result = utils.RunCmd(cmd)
934
    if result.failed:
935
      base.ThrowError("drbd%d: can't shutdown network: %s",
936
                      minor, result.output)
937

    
938
  def Shutdown(self):
939
    """Shutdown the DRBD device.
940

941
    """
942
    if self.minor is None and not self.Attach():
943
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
944
      return
945

    
946
    try:
947
      DRBD8.ShutdownAll(self.minor)
948
    finally:
949
      self.minor = None
950
      self.dev_path = None
951

    
952
  def Remove(self):
953
    """Stub remove for DRBD devices.
954

955
    """
956
    self.Shutdown()
957

    
958
  def Rename(self, new_id):
959
    """Rename a device.
960

961
    This is not supported for drbd devices.
962

963
    """
964
    raise errors.ProgrammerError("Can't rename a drbd device")
965

    
966
  def Grow(self, amount, dryrun, backingstore, excl_stor):
967
    """Resize the DRBD device and its backing storage.
968

969
    See L{BlockDev.Grow} for parameter description.
970

971
    """
972
    if self.minor is None:
973
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
974
    if len(self._children) != 2 or None in self._children:
975
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
976
    self._children[0].Grow(amount, dryrun, backingstore, excl_stor)
977
    if dryrun or backingstore:
978
      # DRBD does not support dry-run mode and is not backing storage,
979
      # so we'll return here
980
      return
981
    cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
982
    result = utils.RunCmd(cmd)
983
    if result.failed:
984
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
985

    
986
  @classmethod
987
  def _InitMeta(cls, minor, dev_path):
988
    """Initialize a meta device.
989

990
    This will not work if the given minor is in use.
991

992
    @type minor: int
993
    @param minor: the DRBD minor whose (future) meta device should be
994
      initialized
995
    @type dev_path: string
996
    @param dev_path: path to the meta device to initialize
997

998
    """
999
    # Zero the metadata first, in order to make sure drbdmeta doesn't
1000
    # try to auto-detect existing filesystems or similar (see
1001
    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
1002
    # care about the first 128MB of data in the device, even though it
1003
    # can be bigger
1004
    result = utils.RunCmd([constants.DD_CMD,
1005
                           "if=/dev/zero", "of=%s" % dev_path,
1006
                           "bs=1048576", "count=128", "oflag=direct"])
1007
    if result.failed:
1008
      base.ThrowError("Can't wipe the meta device: %s", result.output)
1009

    
1010
    info = DRBD8.GetProcInfo()
1011
    cmd_gen = DRBD8.GetCmdGenerator(info)
1012
    cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
1013

    
1014
    result = utils.RunCmd(cmd)
1015
    if result.failed:
1016
      base.ThrowError("Can't initialize meta device: %s", result.output)
1017

    
1018
  @classmethod
1019
  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
1020
             dyn_params):
1021
    """Create a new DRBD8 device.
1022

1023
    Since DRBD devices are not created per se, just assembled, this
1024
    function only initializes the metadata.
1025

1026
    """
1027
    if len(children) != 2:
1028
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1029
    if excl_stor:
1030
      raise errors.ProgrammerError("DRBD device requested with"
1031
                                   " exclusive_storage")
1032
    if constants.DDP_LOCAL_MINOR not in dyn_params:
1033
      raise errors.ProgrammerError("Invalid dynamic params for drbd device %s"
1034
                                   % dyn_params)
1035
    # check that the minor is unused
1036
    aminor = dyn_params[constants.DDP_LOCAL_MINOR]
1037

    
1038
    info = DRBD8.GetProcInfo()
1039
    if info.HasMinorStatus(aminor):
1040
      status = info.GetMinorStatus(aminor)
1041
      in_use = status.is_in_use
1042
    else:
1043
      in_use = False
1044
    if in_use:
1045
      base.ThrowError("drbd%d: minor is already in use at Create() time",
1046
                      aminor)
1047
    meta = children[1]
1048
    meta.Assemble()
1049
    if not meta.Attach():
1050
      base.ThrowError("drbd%d: can't attach to meta device '%s'",
1051
                      aminor, meta)
1052
    cls._CheckMetaSize(meta.dev_path)
1053
    cls._InitMeta(aminor, meta.dev_path)
1054
    return cls(unique_id, children, size, params, dyn_params)
1055

    
1056

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

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

1062
  @type path: string
1063

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