Statistics
| Branch: | Tag: | Revision:

root / lib / storage / drbd.py @ 653bc0f1

History | View | Annotate | Download (35.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.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, *args):
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, *args)
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
    # Once the assembly is over, try to set the synchronization parameters
457
    try:
458
      # The minor may not have been set yet, requiring us to set it at least
459
      # temporarily
460
      old_minor = self.minor
461
      self._SetFromMinor(minor)
462
      sync_errors = self.SetSyncParams(self.params)
463
      if sync_errors:
464
        base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
465
                        (self.minor, utils.CommaJoin(sync_errors)))
466
    finally:
467
      # Undo the change, regardless of whether it will have to be done again
468
      # soon
469
      self._SetFromMinor(old_minor)
470

    
471
  @staticmethod
472
  def _GetNetFamily(minor, lhost, rhost):
473
    if netutils.IP6Address.IsValid(lhost):
474
      if not netutils.IP6Address.IsValid(rhost):
475
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
476
                        (minor, lhost, rhost))
477
      return "ipv6"
478
    elif netutils.IP4Address.IsValid(lhost):
479
      if not netutils.IP4Address.IsValid(rhost):
480
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
481
                        (minor, lhost, rhost))
482
      return "ipv4"
483
    else:
484
      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
485

    
486
  def AddChildren(self, devices):
487
    """Add a disk to the DRBD device.
488

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

493
    """
494
    if self.minor is None:
495
      base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
496
                      self._aminor)
497
    if len(devices) != 2:
498
      base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
499
    info = self._GetShowInfo(self.minor)
500
    if "local_dev" in info:
501
      base.ThrowError("drbd%d: already attached to a local disk", self.minor)
502
    backend, meta = devices
503
    if backend.dev_path is None or meta.dev_path is None:
504
      base.ThrowError("drbd%d: children not ready during AddChildren",
505
                      self.minor)
506
    backend.Open()
507
    meta.Open()
508
    self._CheckMetaSize(meta.dev_path)
509
    self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path)
510

    
511
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
512
    self._children = devices
513

    
514
  def RemoveChildren(self, devices):
515
    """Detach the drbd device from local storage.
516

517
    @type devices: list of L{BlockDev}
518
    @param devices: a list of exactly two L{BlockDev} objects; the first
519
      denotes the data device, the second the meta device for this DRBD device
520

521
    """
522
    if self.minor is None:
523
      base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
524
                      self._aminor)
525
    # early return if we don't actually have backing storage
526
    info = self._GetShowInfo(self.minor)
527
    if "local_dev" not in info:
528
      return
529
    if len(self._children) != 2:
530
      base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
531
                      self._children)
532
    if self._children.count(None) == 2: # we don't actually have children :)
533
      logging.warning("drbd%d: requested detach while detached", self.minor)
534
      return
535
    if len(devices) != 2:
536
      base.ThrowError("drbd%d: we need two children in RemoveChildren",
537
                      self.minor)
538
    for child, dev in zip(self._children, devices):
539
      if dev != child.dev_path:
540
        base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
541
                        " RemoveChildren", self.minor, dev, child.dev_path)
542

    
543
    self._ShutdownLocal(self.minor)
544
    self._children = []
545

    
546
  def _SetMinorSyncParams(self, minor, params):
547
    """Set the parameters of the DRBD syncer.
548

549
    This is the low-level implementation.
550

551
    @type minor: int
552
    @param minor: the drbd minor whose settings we change
553
    @type params: dict
554
    @param params: LD level disk parameters related to the synchronization
555
    @rtype: list
556
    @return: a list of error messages
557

558
    """
559
    cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
560
    result = utils.RunCmd(cmd)
561
    if result.failed:
562
      msg = ("Can't change syncer rate: %s - %s" %
563
             (result.fail_reason, result.output))
564
      logging.error(msg)
565
      return [msg]
566

    
567
    return []
568

    
569
  def SetSyncParams(self, params):
570
    """Set the synchronization parameters of the DRBD syncer.
571

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

574
    """
575
    if self.minor is None:
576
      err = "Not attached during SetSyncParams"
577
      logging.info(err)
578
      return [err]
579

    
580
    children_result = super(DRBD8Dev, self).SetSyncParams(params)
581
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
582
    return children_result
583

    
584
  def PauseResumeSync(self, pause):
585
    """Pauses or resumes the sync of a DRBD device.
586

587
    See L{BlockDev.PauseResumeSync} for parameter description.
588

589
    """
590
    if self.minor is None:
591
      logging.info("Not attached during PauseSync")
592
      return False
593

    
594
    children_result = super(DRBD8Dev, self).PauseResumeSync(pause)
595

    
596
    if pause:
597
      cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
598
    else:
599
      cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
600

    
601
    result = utils.RunCmd(cmd)
602
    if result.failed:
603
      logging.error("Can't %s: %s - %s", cmd,
604
                    result.fail_reason, result.output)
605
    return not result.failed and children_result
606

    
607
  def GetProcStatus(self):
608
    """Return the current status data from /proc/drbd for this device.
609

610
    @rtype: DRBD8Status
611

612
    """
613
    if self.minor is None:
614
      base.ThrowError("drbd%d: GetStats() called while not attached",
615
                      self._aminor)
616
    info = DRBD8.GetProcInfo()
617
    if not info.HasMinorStatus(self.minor):
618
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
619
    return info.GetMinorStatus(self.minor)
620

    
621
  def GetSyncStatus(self):
622
    """Returns the sync status of the device.
623

624
    If sync_percent is None, it means all is ok
625
    If estimated_time is None, it means we can't estimate
626
    the time needed, otherwise it's the time left in seconds.
627

628
    We set the is_degraded parameter to True on two conditions:
629
    network not connected or local disk missing.
630

631
    We compute the ldisk parameter based on whether we have a local
632
    disk or not.
633

634
    @rtype: objects.BlockDevStatus
635

636
    """
637
    if self.minor is None and not self.Attach():
638
      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
639

    
640
    stats = self.GetProcStatus()
641
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
642

    
643
    if stats.is_disk_uptodate:
644
      ldisk_status = constants.LDS_OKAY
645
    elif stats.is_diskless:
646
      ldisk_status = constants.LDS_FAULTY
647
    else:
648
      ldisk_status = constants.LDS_UNKNOWN
649

    
650
    return objects.BlockDevStatus(dev_path=self.dev_path,
651
                                  major=self.major,
652
                                  minor=self.minor,
653
                                  sync_percent=stats.sync_percent,
654
                                  estimated_time=stats.est_time,
655
                                  is_degraded=is_degraded,
656
                                  ldisk_status=ldisk_status)
657

    
658
  def Open(self, force=False):
659
    """Make the local state primary.
660

661
    If the 'force' parameter is given, DRBD is instructed to switch the device
662
    into primary mode. Since this is a potentially dangerous operation, the
663
    force flag should be only given after creation, when it actually is
664
    mandatory.
665

666
    """
667
    if self.minor is None and not self.Attach():
668
      logging.error("DRBD cannot attach to a device during open")
669
      return False
670

    
671
    cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
672

    
673
    result = utils.RunCmd(cmd)
674
    if result.failed:
675
      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
676
                      result.output)
677

    
678
  def Close(self):
679
    """Make the local state secondary.
680

681
    This will, of course, fail if the device is in use.
682

683
    """
684
    if self.minor is None and not self.Attach():
685
      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
686
    cmd = self._cmd_gen.GenSecondaryCmd(self.minor)
687
    result = utils.RunCmd(cmd)
688
    if result.failed:
689
      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
690
                      self.minor, result.output)
691

    
692
  def DisconnectNet(self):
693
    """Removes network configuration.
694

695
    This method shutdowns the network side of the device.
696

697
    The method will wait up to a hardcoded timeout for the device to
698
    go into standalone after the 'disconnect' command before
699
    re-configuring it, as sometimes it takes a while for the
700
    disconnect to actually propagate and thus we might issue a 'net'
701
    command while the device is still connected. If the device will
702
    still be attached to the network and we time out, we raise an
703
    exception.
704

705
    """
706
    if self.minor is None:
707
      base.ThrowError("drbd%d: disk not attached in re-attach net",
708
                      self._aminor)
709

    
710
    if None in (self._lhost, self._lport, self._rhost, self._rport):
711
      base.ThrowError("drbd%d: DRBD disk missing network info in"
712
                      " DisconnectNet()", self.minor)
713

    
714
    class _DisconnectStatus:
715
      def __init__(self, ever_disconnected):
716
        self.ever_disconnected = ever_disconnected
717

    
718
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
719

    
720
    def _WaitForDisconnect():
721
      if self.GetProcStatus().is_standalone:
722
        return
723

    
724
      # retry the disconnect, it seems possible that due to a well-time
725
      # disconnect on the peer, my disconnect command might be ignored and
726
      # forgotten
727
      dstatus.ever_disconnected = \
728
        base.IgnoreError(self._ShutdownNet, self.minor) or \
729
        dstatus.ever_disconnected
730

    
731
      raise utils.RetryAgain()
732

    
733
    # Keep start time
734
    start_time = time.time()
735

    
736
    try:
737
      # Start delay at 100 milliseconds and grow up to 2 seconds
738
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
739
                  self._NET_RECONFIG_TIMEOUT)
740
    except utils.RetryTimeout:
741
      if dstatus.ever_disconnected:
742
        msg = ("drbd%d: device did not react to the"
743
               " 'disconnect' command in a timely manner")
744
      else:
745
        msg = "drbd%d: can't shutdown network, even after multiple retries"
746

    
747
      base.ThrowError(msg, self.minor)
748

    
749
    reconfig_time = time.time() - start_time
750
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
751
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
752
                   self.minor, reconfig_time)
753

    
754
  def AttachNet(self, multimaster):
755
    """Reconnects the network.
756

757
    This method connects the network side of the device with a
758
    specified multi-master flag. The device needs to be 'Standalone'
759
    but have valid network configuration data.
760

761
    @type multimaster: boolean
762
    @param multimaster: init the network in dual-primary mode
763

764
    """
765
    if self.minor is None:
766
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
767

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

    
771
    status = self.GetProcStatus()
772

    
773
    if not status.is_standalone:
774
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
775
                      self.minor)
776

    
777
    self._AssembleNet(self.minor,
778
                      (self._lhost, self._lport, self._rhost, self._rport),
779
                      dual_pri=multimaster, hmac=constants.DRBD_HMAC_ALG,
780
                      secret=self._secret)
781

    
782
  def Attach(self):
783
    """Check if our minor is configured.
784

785
    This doesn't do any device configurations - it only checks if the
786
    minor is in a state different from Unconfigured.
787

788
    Note that this function will not change the state of the system in
789
    any way (except in case of side-effects caused by reading from
790
    /proc).
791

792
    """
793
    used_devs = DRBD8.GetUsedDevs()
794
    if self._aminor in used_devs:
795
      minor = self._aminor
796
    else:
797
      minor = None
798

    
799
    self._SetFromMinor(minor)
800
    return minor is not None
801

    
802
  def Assemble(self):
803
    """Assemble the drbd.
804

805
    Method:
806
      - if we have a configured device, we try to ensure that it matches
807
        our config
808
      - if not, we create it from zero
809
      - anyway, set the device parameters
810

811
    """
812
    super(DRBD8Dev, self).Assemble()
813

    
814
    self.Attach()
815
    if self.minor is None:
816
      # local device completely unconfigured
817
      self._FastAssemble()
818
    else:
819
      # we have to recheck the local and network status and try to fix
820
      # the device
821
      self._SlowAssemble()
822

    
823
  def _SlowAssemble(self):
824
    """Assembles the DRBD device from a (partially) configured device.
825

826
    In case of partially attached (local device matches but no network
827
    setup), we perform the network attach. If successful, we re-test
828
    the attach if can return success.
829

830
    """
831
    # TODO: Rewrite to not use a for loop just because there is 'break'
832
    # pylint: disable=W0631
833
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
834
    for minor in (self._aminor,):
835
      info = self._GetShowInfo(minor)
836
      match_l = self._MatchesLocal(info)
837
      match_r = self._MatchesNet(info)
838

    
839
      if match_l and match_r:
840
        # everything matches
841
        break
842

    
843
      if match_l and not match_r and "local_addr" not in info:
844
        # disk matches, but not attached to network, attach and recheck
845
        self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG,
846
                          secret=self._secret)
847
        if self._MatchesNet(self._GetShowInfo(minor)):
848
          break
849
        else:
850
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
851
                          " show' disagrees", minor)
852

    
853
      if match_r and "local_dev" not in info:
854
        # no local disk, but network attached and it matches
855
        self._AssembleLocal(minor, self._children[0].dev_path,
856
                            self._children[1].dev_path, self.size)
857
        if self._MatchesLocal(self._GetShowInfo(minor)):
858
          break
859
        else:
860
          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
861
                          " show' disagrees", minor)
862

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

    
890
    else:
891
      minor = None
892

    
893
    self._SetFromMinor(minor)
894
    if minor is None:
895
      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
896
                      self._aminor)
897

    
898
  def _FastAssemble(self):
899
    """Assemble the drbd device from zero.
900

901
    This is run when in Assemble we detect our minor is unused.
902

903
    """
904
    minor = self._aminor
905
    if self._children and self._children[0] and self._children[1]:
906
      self._AssembleLocal(minor, self._children[0].dev_path,
907
                          self._children[1].dev_path, self.size)
908
    if self._lhost and self._lport and self._rhost and self._rport:
909
      self._AssembleNet(minor,
910
                        (self._lhost, self._lport, self._rhost, self._rport),
911
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
912
    self._SetFromMinor(minor)
913

    
914
  def _ShutdownLocal(self, minor):
915
    """Detach from the local device.
916

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

920
    @type minor: int
921
    @param minor: the device to detach from the local device
922

923
    """
924
    cmd = self._cmd_gen.GenDetachCmd(minor)
925
    result = utils.RunCmd(cmd)
926
    if result.failed:
927
      base.ThrowError("drbd%d: can't detach local disk: %s",
928
                      minor, result.output)
929

    
930
  def _ShutdownNet(self, minor):
931
    """Disconnect from the remote peer.
932

933
    This fails if we don't have a local device.
934

935
    @type minor: boolean
936
    @param minor: the device to disconnect from the remote peer
937

938
    """
939
    family = self._GetNetFamily(minor, self._lhost, self._rhost)
940
    cmd = self._cmd_gen.GenDisconnectCmd(minor, family,
941
                                         self._lhost, self._lport,
942
                                         self._rhost, self._rport)
943
    result = utils.RunCmd(cmd)
944
    if result.failed:
945
      base.ThrowError("drbd%d: can't shutdown network: %s",
946
                      minor, result.output)
947

    
948
  def Shutdown(self):
949
    """Shutdown the DRBD device.
950

951
    """
952
    if self.minor is None and not self.Attach():
953
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
954
      return
955

    
956
    try:
957
      DRBD8.ShutdownAll(self.minor)
958
    finally:
959
      self.minor = None
960
      self.dev_path = None
961

    
962
  def Remove(self):
963
    """Stub remove for DRBD devices.
964

965
    """
966
    self.Shutdown()
967

    
968
  def Rename(self, new_id):
969
    """Rename a device.
970

971
    This is not supported for drbd devices.
972

973
    """
974
    raise errors.ProgrammerError("Can't rename a drbd device")
975

    
976
  def Grow(self, amount, dryrun, backingstore, excl_stor):
977
    """Resize the DRBD device and its backing storage.
978

979
    See L{BlockDev.Grow} for parameter description.
980

981
    """
982
    if self.minor is None:
983
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
984
    if len(self._children) != 2 or None in self._children:
985
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
986
    self._children[0].Grow(amount, dryrun, backingstore, excl_stor)
987
    if dryrun or backingstore:
988
      # DRBD does not support dry-run mode and is not backing storage,
989
      # so we'll return here
990
      return
991
    cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
992
    result = utils.RunCmd(cmd)
993
    if result.failed:
994
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
995

    
996
  @classmethod
997
  def _InitMeta(cls, minor, dev_path):
998
    """Initialize a meta device.
999

1000
    This will not work if the given minor is in use.
1001

1002
    @type minor: int
1003
    @param minor: the DRBD minor whose (future) meta device should be
1004
      initialized
1005
    @type dev_path: string
1006
    @param dev_path: path to the meta device to initialize
1007

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

    
1020
    info = DRBD8.GetProcInfo()
1021
    cmd_gen = DRBD8.GetCmdGenerator(info)
1022
    cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
1023

    
1024
    result = utils.RunCmd(cmd)
1025
    if result.failed:
1026
      base.ThrowError("Can't initialize meta device: %s", result.output)
1027

    
1028
  @classmethod
1029
  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
1030
             dyn_params, *_):
1031
    """Create a new DRBD8 device.
1032

1033
    Since DRBD devices are not created per se, just assembled, this
1034
    function only initializes the metadata.
1035

1036
    """
1037
    if len(children) != 2:
1038
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1039
    if excl_stor:
1040
      raise errors.ProgrammerError("DRBD device requested with"
1041
                                   " exclusive_storage")
1042
    if constants.DDP_LOCAL_MINOR not in dyn_params:
1043
      raise errors.ProgrammerError("Invalid dynamic params for drbd device %s"
1044
                                   % dyn_params)
1045
    # check that the minor is unused
1046
    aminor = dyn_params[constants.DDP_LOCAL_MINOR]
1047

    
1048
    info = DRBD8.GetProcInfo()
1049
    if info.HasMinorStatus(aminor):
1050
      status = info.GetMinorStatus(aminor)
1051
      in_use = status.is_in_use
1052
    else:
1053
      in_use = False
1054
    if in_use:
1055
      base.ThrowError("drbd%d: minor is already in use at Create() time",
1056
                      aminor)
1057
    meta = children[1]
1058
    meta.Assemble()
1059
    if not meta.Attach():
1060
      base.ThrowError("drbd%d: can't attach to meta device '%s'",
1061
                      aminor, meta)
1062
    cls._CheckMetaSize(meta.dev_path)
1063
    cls._InitMeta(aminor, meta.dev_path)
1064
    return cls(unique_id, children, size, params, dyn_params)
1065

    
1066

    
1067
def _CanReadDevice(path):
1068
  """Check if we can read from the given device.
1069

1070
  This tries to read the first 128k of the device.
1071

1072
  @type path: string
1073

1074
  """
1075
  try:
1076
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1077
    return True
1078
  except EnvironmentError:
1079
    logging.warning("Can't read from device %s", path, exc_info=True)
1080
    return False