4 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
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.
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.
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
22 """DRBD block device related functionality"""
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
39 # Size of reads in _CanReadDevice
41 _DEVICE_READ_SIZE = 128 * 1024
45 """Various methods to deals with the DRBD system as a whole.
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.
51 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
56 def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
57 """Returns DRBD usermode_helper currently set.
59 @type filename: string
60 @param filename: the filename to read the usermode helper from
62 @return: the currently configured DRBD usermode helper
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))
72 base.ThrowError("Can't read DRBD helper file %s: %s",
75 base.ThrowError("Can't read any data from %s", filename)
80 """Reads and parses information from /proc/drbd.
83 @return: a L{DRBD8Info} instance containing the current /proc/drbd info
86 return DRBD8Info.CreateFromFile()
90 """Compute the list of used DRBD minors.
95 info = DRBD8.GetProcInfo()
96 return filter(lambda m: not info.GetMinorStatus(m).is_unconfigured,
100 def FindUnusedMinor():
101 """Find an unused DRBD device.
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.
110 info = DRBD8.GetProcInfo()
111 for minor in info.GetMinors():
112 status = info.GetMinorStatus(minor)
113 if not status.is_in_use:
115 highest = max(highest, minor)
117 if highest is None: # there are no minors in use at all
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")
126 def GetCmdGenerator(info):
127 """Creates a suitable L{BaseDRBDCmdGenerator} based on the given info.
129 @type info: DRBD8Info
130 @rtype: BaseDRBDCmdGenerator
133 version = info.GetVersion()
134 if version["k_minor"] <= 3:
135 return drbd_cmdgen.DRBD83CmdGenerator(version)
137 return drbd_cmdgen.DRBD84CmdGenerator(version)
140 def ShutdownAll(minor):
141 """Deactivate the device.
143 This will, of course, fail if the device is in use.
146 @param minor: the minor to shut down
149 info = DRBD8.GetProcInfo()
150 cmd_gen = DRBD8.GetCmdGenerator(info)
152 cmd = cmd_gen.GenDownCmd(minor)
153 result = utils.RunCmd(cmd)
155 base.ThrowError("drbd%d: can't shutdown drbd device: %s",
156 minor, result.output)
159 class DRBD8Dev(base.BlockDev):
160 """DRBD v8.x block device.
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.
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.
175 _NET_RECONFIG_TIMEOUT = 60
177 def __init__(self, unique_id, children, size, params):
178 if children and children.count(None) > 0:
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
188 if not _CanReadDevice(children[1].dev_path):
189 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
191 super(DRBD8Dev, self).__init__(unique_id, children, size, params)
192 self.major = self._DRBD_MAJOR
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"])
201 if version["k_minor"] <= 3:
202 self._show_info_cls = drbd_info.DRBD83ShowInfo
204 self._show_info_cls = drbd_info.DRBD84ShowInfo
206 self._cmd_gen = DRBD8.GetCmdGenerator(info)
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" %
216 """Return the path to a drbd device for a given minor.
222 return "/dev/drbd%d" % minor
224 def _SetFromMinor(self, minor):
225 """Set our parameters based on the given minor.
227 This sets our minor variable and our dev_path.
233 self.minor = self.dev_path = None
234 self.attached = False
237 self.dev_path = self._DevPath(minor)
241 def _CheckMetaSize(meta_device):
242 """Check if the given meta device looks like a valid one.
244 This currently only checks the size, which must be around
247 @type meta_device: string
248 @param meta_device: the path to the device to check
251 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
253 base.ThrowError("Failed to get device size: %s - %s",
254 result.fail_reason, result.output)
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))
273 def _GetShowData(self, minor):
274 """Return the `drbdsetup show` data.
277 @param minor: the minor to collect show output for
281 result = utils.RunCmd(self._cmd_gen.GenShowCmd(minor))
283 logging.error("Can't display the drbd config: %s - %s",
284 result.fail_reason, result.output)
288 def _GetShowInfo(self, minor):
289 """Return parsed information from `drbdsetup show`.
292 @param minor: the minor to return information for
293 @rtype: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
296 return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
298 def _MatchesLocal(self, info):
299 """Test if our local config matches with an existing device.
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
306 @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
311 backend, meta = self._children
313 backend = meta = None
315 if backend is not None:
316 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
318 retval = ("local_dev" not in info)
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
326 retval = retval and ("meta_dev" not in info and
327 "meta_index" not in info)
330 def _MatchesNet(self, info):
331 """Test if our network config matches with an existing device.
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
338 @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
342 if (((self._lhost is None and not ("local_addr" in info)) and
343 (self._rhost is None and not ("remote_addr" in info)))):
346 if self._lhost is None:
349 if not ("local_addr" in info and
350 "remote_addr" in info):
353 retval = (info["local_addr"] == (self._lhost, self._lport))
355 info["remote_addr"] == (self._rhost, self._rport))
358 def _AssembleLocal(self, minor, backend, meta, size):
359 """Configure the local part of a DRBD device.
362 @param minor: the minor to assemble locally
363 @type backend: string
364 @param backend: path to the data device to use
366 @param meta: path to the meta device to use
368 @param size: size in MiB
371 cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta,
375 result = utils.RunCmd(cmd)
377 base.ThrowError("drbd%d: can't attach local disk: %s",
378 minor, result.output)
380 def _AssembleNet(self, minor, net_info, dual_pri=False, hmac=None,
382 """Configure the network part of the device.
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 dual_pri: boolean
390 @param dual_pri: whether two primaries should be allowed or not
392 @param hmac: the HMAC algorithm to use
394 @param secret: the shared secret to use
397 lhost, lport, rhost, rport = net_info
399 # we don't want network connection and actually want to make
401 self._ShutdownNet(minor)
405 protocol = constants.DRBD_MIGRATION_NET_PROTOCOL
407 protocol = self.params[constants.LDP_PROTOCOL]
409 # Workaround for a race condition. When DRBD is doing its dance to
410 # establish a connection with its peer, it also sends the
411 # synchronization speed over the wire. In some cases setting the
412 # sync speed only after setting up both sides can race with DRBD
413 # connecting, hence we set it here before telling DRBD anything
415 sync_errors = self._SetMinorSyncParams(minor, self.params)
417 base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
418 (minor, utils.CommaJoin(sync_errors)))
420 family = self._GetNetFamily(minor, lhost, rhost)
422 cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport,
423 rhost, rport, protocol,
424 dual_pri, hmac, secret, self.params)
426 result = utils.RunCmd(cmd)
428 base.ThrowError("drbd%d: can't setup network: %s - %s",
429 minor, result.fail_reason, result.output)
431 def _CheckNetworkConfig():
432 info = self._GetShowInfo(minor)
433 if not "local_addr" in info or not "remote_addr" in info:
434 raise utils.RetryAgain()
436 if (info["local_addr"] != (lhost, lport) or
437 info["remote_addr"] != (rhost, rport)):
438 raise utils.RetryAgain()
441 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
442 except utils.RetryTimeout:
443 base.ThrowError("drbd%d: timeout while configuring network", minor)
446 def _GetNetFamily(minor, lhost, rhost):
447 if netutils.IP6Address.IsValid(lhost):
448 if not netutils.IP6Address.IsValid(rhost):
449 base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
450 (minor, lhost, rhost))
452 elif netutils.IP4Address.IsValid(lhost):
453 if not netutils.IP4Address.IsValid(rhost):
454 base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
455 (minor, lhost, rhost))
458 base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
460 def AddChildren(self, devices):
461 """Add a disk to the DRBD device.
463 @type devices: list of L{BlockDev}
464 @param devices: a list of exactly two L{BlockDev} objects; the first
465 denotes the data device, the second the meta device for this DRBD device
468 if self.minor is None:
469 base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
471 if len(devices) != 2:
472 base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
473 info = self._GetShowInfo(self.minor)
474 if "local_dev" in info:
475 base.ThrowError("drbd%d: already attached to a local disk", self.minor)
476 backend, meta = devices
477 if backend.dev_path is None or meta.dev_path is None:
478 base.ThrowError("drbd%d: children not ready during AddChildren",
482 self._CheckMetaSize(meta.dev_path)
483 self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path)
485 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
486 self._children = devices
488 def RemoveChildren(self, devices):
489 """Detach the drbd device from local storage.
491 @type devices: list of L{BlockDev}
492 @param devices: a list of exactly two L{BlockDev} objects; the first
493 denotes the data device, the second the meta device for this DRBD device
496 if self.minor is None:
497 base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
499 # early return if we don't actually have backing storage
500 info = self._GetShowInfo(self.minor)
501 if "local_dev" not in info:
503 if len(self._children) != 2:
504 base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
506 if self._children.count(None) == 2: # we don't actually have children :)
507 logging.warning("drbd%d: requested detach while detached", self.minor)
509 if len(devices) != 2:
510 base.ThrowError("drbd%d: we need two children in RemoveChildren",
512 for child, dev in zip(self._children, devices):
513 if dev != child.dev_path:
514 base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
515 " RemoveChildren", self.minor, dev, child.dev_path)
517 self._ShutdownLocal(self.minor)
520 def _SetMinorSyncParams(self, minor, params):
521 """Set the parameters of the DRBD syncer.
523 This is the low-level implementation.
526 @param minor: the drbd minor whose settings we change
528 @param params: LD level disk parameters related to the synchronization
530 @return: a list of error messages
533 cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
534 result = utils.RunCmd(cmd)
536 msg = ("Can't change syncer rate: %s - %s" %
537 (result.fail_reason, result.output))
543 def SetSyncParams(self, params):
544 """Set the synchronization parameters of the DRBD syncer.
546 See L{BlockDev.SetSyncParams} for parameter description.
549 if self.minor is None:
550 err = "Not attached during SetSyncParams"
554 children_result = super(DRBD8Dev, self).SetSyncParams(params)
555 children_result.extend(self._SetMinorSyncParams(self.minor, params))
556 return children_result
558 def PauseResumeSync(self, pause):
559 """Pauses or resumes the sync of a DRBD device.
561 See L{BlockDev.PauseResumeSync} for parameter description.
564 if self.minor is None:
565 logging.info("Not attached during PauseSync")
568 children_result = super(DRBD8Dev, self).PauseResumeSync(pause)
571 cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
573 cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
575 result = utils.RunCmd(cmd)
577 logging.error("Can't %s: %s - %s", cmd,
578 result.fail_reason, result.output)
579 return not result.failed and children_result
581 def GetProcStatus(self):
582 """Return the current status data from /proc/drbd for this device.
587 if self.minor is None:
588 base.ThrowError("drbd%d: GetStats() called while not attached",
590 info = DRBD8.GetProcInfo()
591 if not info.HasMinorStatus(self.minor):
592 base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
593 return info.GetMinorStatus(self.minor)
595 def GetSyncStatus(self):
596 """Returns the sync status of the device.
598 If sync_percent is None, it means all is ok
599 If estimated_time is None, it means we can't estimate
600 the time needed, otherwise it's the time left in seconds.
602 We set the is_degraded parameter to True on two conditions:
603 network not connected or local disk missing.
605 We compute the ldisk parameter based on whether we have a local
608 @rtype: objects.BlockDevStatus
611 if self.minor is None and not self.Attach():
612 base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
614 stats = self.GetProcStatus()
615 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
617 if stats.is_disk_uptodate:
618 ldisk_status = constants.LDS_OKAY
619 elif stats.is_diskless:
620 ldisk_status = constants.LDS_FAULTY
622 ldisk_status = constants.LDS_UNKNOWN
624 return objects.BlockDevStatus(dev_path=self.dev_path,
627 sync_percent=stats.sync_percent,
628 estimated_time=stats.est_time,
629 is_degraded=is_degraded,
630 ldisk_status=ldisk_status)
632 def Open(self, force=False):
633 """Make the local state primary.
635 If the 'force' parameter is given, DRBD is instructed to switch the device
636 into primary mode. Since this is a potentially dangerous operation, the
637 force flag should be only given after creation, when it actually is
641 if self.minor is None and not self.Attach():
642 logging.error("DRBD cannot attach to a device during open")
645 cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
647 result = utils.RunCmd(cmd)
649 base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
653 """Make the local state secondary.
655 This will, of course, fail if the device is in use.
658 if self.minor is None and not self.Attach():
659 base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
660 cmd = self._cmd_gen.GenSecondaryCmd(self.minor)
661 result = utils.RunCmd(cmd)
663 base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
664 self.minor, result.output)
666 def DisconnectNet(self):
667 """Removes network configuration.
669 This method shutdowns the network side of the device.
671 The method will wait up to a hardcoded timeout for the device to
672 go into standalone after the 'disconnect' command before
673 re-configuring it, as sometimes it takes a while for the
674 disconnect to actually propagate and thus we might issue a 'net'
675 command while the device is still connected. If the device will
676 still be attached to the network and we time out, we raise an
680 if self.minor is None:
681 base.ThrowError("drbd%d: disk not attached in re-attach net",
684 if None in (self._lhost, self._lport, self._rhost, self._rport):
685 base.ThrowError("drbd%d: DRBD disk missing network info in"
686 " DisconnectNet()", self.minor)
688 class _DisconnectStatus:
689 def __init__(self, ever_disconnected):
690 self.ever_disconnected = ever_disconnected
692 dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
694 def _WaitForDisconnect():
695 if self.GetProcStatus().is_standalone:
698 # retry the disconnect, it seems possible that due to a well-time
699 # disconnect on the peer, my disconnect command might be ignored and
701 dstatus.ever_disconnected = \
702 base.IgnoreError(self._ShutdownNet, self.minor) or \
703 dstatus.ever_disconnected
705 raise utils.RetryAgain()
708 start_time = time.time()
711 # Start delay at 100 milliseconds and grow up to 2 seconds
712 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
713 self._NET_RECONFIG_TIMEOUT)
714 except utils.RetryTimeout:
715 if dstatus.ever_disconnected:
716 msg = ("drbd%d: device did not react to the"
717 " 'disconnect' command in a timely manner")
719 msg = "drbd%d: can't shutdown network, even after multiple retries"
721 base.ThrowError(msg, self.minor)
723 reconfig_time = time.time() - start_time
724 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
725 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
726 self.minor, reconfig_time)
728 def AttachNet(self, multimaster):
729 """Reconnects the network.
731 This method connects the network side of the device with a
732 specified multi-master flag. The device needs to be 'Standalone'
733 but have valid network configuration data.
735 @type multimaster: boolean
736 @param multimaster: init the network in dual-primary mode
739 if self.minor is None:
740 base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
742 if None in (self._lhost, self._lport, self._rhost, self._rport):
743 base.ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
745 status = self.GetProcStatus()
747 if not status.is_standalone:
748 base.ThrowError("drbd%d: device is not standalone in AttachNet",
751 self._AssembleNet(self.minor,
752 (self._lhost, self._lport, self._rhost, self._rport),
753 dual_pri=multimaster, hmac=constants.DRBD_HMAC_ALG,
757 """Check if our minor is configured.
759 This doesn't do any device configurations - it only checks if the
760 minor is in a state different from Unconfigured.
762 Note that this function will not change the state of the system in
763 any way (except in case of side-effects caused by reading from
767 used_devs = DRBD8.GetUsedDevs()
768 if self._aminor in used_devs:
773 self._SetFromMinor(minor)
774 return minor is not None
777 """Assemble the drbd.
780 - if we have a configured device, we try to ensure that it matches
782 - if not, we create it from zero
783 - anyway, set the device parameters
786 super(DRBD8Dev, self).Assemble()
789 if self.minor is None:
790 # local device completely unconfigured
793 # we have to recheck the local and network status and try to fix
797 sync_errors = self.SetSyncParams(self.params)
799 base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
800 (self.minor, utils.CommaJoin(sync_errors)))
802 def _SlowAssemble(self):
803 """Assembles the DRBD device from a (partially) configured device.
805 In case of partially attached (local device matches but no network
806 setup), we perform the network attach. If successful, we re-test
807 the attach if can return success.
810 # TODO: Rewrite to not use a for loop just because there is 'break'
811 # pylint: disable=W0631
812 net_data = (self._lhost, self._lport, self._rhost, self._rport)
813 for minor in (self._aminor,):
814 info = self._GetShowInfo(minor)
815 match_l = self._MatchesLocal(info)
816 match_r = self._MatchesNet(info)
818 if match_l and match_r:
822 if match_l and not match_r and "local_addr" not in info:
823 # disk matches, but not attached to network, attach and recheck
824 self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG,
826 if self._MatchesNet(self._GetShowInfo(minor)):
829 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
830 " show' disagrees", minor)
832 if match_r and "local_dev" not in info:
833 # no local disk, but network attached and it matches
834 self._AssembleLocal(minor, self._children[0].dev_path,
835 self._children[1].dev_path, self.size)
836 if self._MatchesLocal(self._GetShowInfo(minor)):
839 base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
840 " show' disagrees", minor)
842 # this case must be considered only if we actually have local
843 # storage, i.e. not in diskless mode, because all diskless
844 # devices are equal from the point of view of local
846 if (match_l and "local_dev" in info and
847 not match_r and "local_addr" in info):
848 # strange case - the device network part points to somewhere
849 # else, even though its local storage is ours; as we own the
850 # drbd space, we try to disconnect from the remote peer and
851 # reconnect to our correct one
853 self._ShutdownNet(minor)
854 except errors.BlockDeviceError, err:
855 base.ThrowError("drbd%d: device has correct local storage, wrong"
856 " remote peer and is unable to disconnect in order"
857 " to attach to the correct peer: %s", minor, str(err))
858 # note: _AssembleNet also handles the case when we don't want
859 # local storage (i.e. one or more of the _[lr](host|port) is
861 self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG,
863 if self._MatchesNet(self._GetShowInfo(minor)):
866 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
867 " show' disagrees", minor)
872 self._SetFromMinor(minor)
874 base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
877 def _FastAssemble(self):
878 """Assemble the drbd device from zero.
880 This is run when in Assemble we detect our minor is unused.
884 if self._children and self._children[0] and self._children[1]:
885 self._AssembleLocal(minor, self._children[0].dev_path,
886 self._children[1].dev_path, self.size)
887 if self._lhost and self._lport and self._rhost and self._rport:
888 self._AssembleNet(minor,
889 (self._lhost, self._lport, self._rhost, self._rport),
890 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
891 self._SetFromMinor(minor)
893 def _ShutdownLocal(self, minor):
894 """Detach from the local device.
896 I/Os will continue to be served from the remote device. If we
897 don't have a remote device, this operation will fail.
900 @param minor: the device to detach from the local device
903 cmd = self._cmd_gen.GenDetachCmd(minor)
904 result = utils.RunCmd(cmd)
906 base.ThrowError("drbd%d: can't detach local disk: %s",
907 minor, result.output)
909 def _ShutdownNet(self, minor):
910 """Disconnect from the remote peer.
912 This fails if we don't have a local device.
915 @param minor: the device to disconnect from the remote peer
918 family = self._GetNetFamily(minor, self._lhost, self._rhost)
919 cmd = self._cmd_gen.GenDisconnectCmd(minor, family,
920 self._lhost, self._lport,
921 self._rhost, self._rport)
922 result = utils.RunCmd(cmd)
924 base.ThrowError("drbd%d: can't shutdown network: %s",
925 minor, result.output)
928 """Shutdown the DRBD device.
931 if self.minor is None and not self.Attach():
932 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
936 DRBD8.ShutdownAll(self.minor)
942 """Stub remove for DRBD devices.
947 def Rename(self, new_id):
950 This is not supported for drbd devices.
953 raise errors.ProgrammerError("Can't rename a drbd device")
955 def Grow(self, amount, dryrun, backingstore, excl_stor):
956 """Resize the DRBD device and its backing storage.
958 See L{BlockDev.Grow} for parameter description.
961 if self.minor is None:
962 base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
963 if len(self._children) != 2 or None in self._children:
964 base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
965 self._children[0].Grow(amount, dryrun, backingstore, excl_stor)
966 if dryrun or backingstore:
967 # DRBD does not support dry-run mode and is not backing storage,
968 # so we'll return here
970 cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
971 result = utils.RunCmd(cmd)
973 base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
976 def _InitMeta(cls, minor, dev_path):
977 """Initialize a meta device.
979 This will not work if the given minor is in use.
982 @param minor: the DRBD minor whose (future) meta device should be
984 @type dev_path: string
985 @param dev_path: path to the meta device to initialize
988 # Zero the metadata first, in order to make sure drbdmeta doesn't
989 # try to auto-detect existing filesystems or similar (see
990 # http://code.google.com/p/ganeti/issues/detail?id=182); we only
991 # care about the first 128MB of data in the device, even though it
993 result = utils.RunCmd([constants.DD_CMD,
994 "if=/dev/zero", "of=%s" % dev_path,
995 "bs=1048576", "count=128", "oflag=direct"])
997 base.ThrowError("Can't wipe the meta device: %s", result.output)
999 info = DRBD8.GetProcInfo()
1000 cmd_gen = DRBD8.GetCmdGenerator(info)
1001 cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
1003 result = utils.RunCmd(cmd)
1005 base.ThrowError("Can't initialize meta device: %s", result.output)
1008 def Create(cls, unique_id, children, size, spindles, params, excl_stor):
1009 """Create a new DRBD8 device.
1011 Since DRBD devices are not created per se, just assembled, this
1012 function only initializes the metadata.
1015 if len(children) != 2:
1016 raise errors.ProgrammerError("Invalid setup for the drbd device")
1018 raise errors.ProgrammerError("DRBD device requested with"
1019 " exclusive_storage")
1020 # check that the minor is unused
1021 aminor = unique_id[4]
1023 info = DRBD8.GetProcInfo()
1024 if info.HasMinorStatus(aminor):
1025 status = info.GetMinorStatus(aminor)
1026 in_use = status.is_in_use
1030 base.ThrowError("drbd%d: minor is already in use at Create() time",
1034 if not meta.Attach():
1035 base.ThrowError("drbd%d: can't attach to meta device '%s'",
1037 cls._CheckMetaSize(meta.dev_path)
1038 cls._InitMeta(aminor, meta.dev_path)
1039 return cls(unique_id, children, size, params)
1042 def _CanReadDevice(path):
1043 """Check if we can read from the given device.
1045 This tries to read the first 128k of the device.
1051 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1053 except EnvironmentError:
1054 logging.warning("Can't read from device %s", path, exc_info=True)