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, protocol,
381 dual_pri=False, hmac=None, secret=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 protocol: string
390 @param protocol: either "ipv4" or "ipv6"
391 @type dual_pri: boolean
392 @param dual_pri: whether two primaries should be allowed or not
394 @param hmac: the HMAC algorithm to use
396 @param secret: the shared secret to use
399 lhost, lport, rhost, rport = net_info
401 # we don't want network connection and actually want to make
403 self._ShutdownNet(minor)
406 # Workaround for a race condition. When DRBD is doing its dance to
407 # establish a connection with its peer, it also sends the
408 # synchronization speed over the wire. In some cases setting the
409 # sync speed only after setting up both sides can race with DRBD
410 # connecting, hence we set it here before telling DRBD anything
412 sync_errors = self._SetMinorSyncParams(minor, self.params)
414 base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
415 (minor, utils.CommaJoin(sync_errors)))
417 family = self._GetNetFamily(minor, lhost, rhost)
419 cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport,
420 rhost, rport, protocol,
421 dual_pri, hmac, secret, self.params)
423 result = utils.RunCmd(cmd)
425 base.ThrowError("drbd%d: can't setup network: %s - %s",
426 minor, result.fail_reason, result.output)
428 def _CheckNetworkConfig():
429 info = self._GetShowInfo(minor)
430 if not "local_addr" in info or not "remote_addr" in info:
431 raise utils.RetryAgain()
433 if (info["local_addr"] != (lhost, lport) or
434 info["remote_addr"] != (rhost, rport)):
435 raise utils.RetryAgain()
438 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
439 except utils.RetryTimeout:
440 base.ThrowError("drbd%d: timeout while configuring network", minor)
443 def _GetNetFamily(minor, lhost, rhost):
444 if netutils.IP6Address.IsValid(lhost):
445 if not netutils.IP6Address.IsValid(rhost):
446 base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
447 (minor, lhost, rhost))
449 elif netutils.IP4Address.IsValid(lhost):
450 if not netutils.IP4Address.IsValid(rhost):
451 base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
452 (minor, lhost, rhost))
455 base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
457 def AddChildren(self, devices):
458 """Add a disk to the DRBD device.
460 @type devices: list of L{BlockDev}
461 @param devices: a list of exactly two L{BlockDev} objects; the first
462 denotes the data device, the second the meta device for this DRBD device
465 if self.minor is None:
466 base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
468 if len(devices) != 2:
469 base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
470 info = self._GetShowInfo(self.minor)
471 if "local_dev" in info:
472 base.ThrowError("drbd%d: already attached to a local disk", self.minor)
473 backend, meta = devices
474 if backend.dev_path is None or meta.dev_path is None:
475 base.ThrowError("drbd%d: children not ready during AddChildren",
479 self._CheckMetaSize(meta.dev_path)
480 self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path)
482 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
483 self._children = devices
485 def RemoveChildren(self, devices):
486 """Detach the drbd device from local storage.
488 @type devices: list of L{BlockDev}
489 @param devices: a list of exactly two L{BlockDev} objects; the first
490 denotes the data device, the second the meta device for this DRBD device
493 if self.minor is None:
494 base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
496 # early return if we don't actually have backing storage
497 info = self._GetShowInfo(self.minor)
498 if "local_dev" not in info:
500 if len(self._children) != 2:
501 base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
503 if self._children.count(None) == 2: # we don't actually have children :)
504 logging.warning("drbd%d: requested detach while detached", self.minor)
506 if len(devices) != 2:
507 base.ThrowError("drbd%d: we need two children in RemoveChildren",
509 for child, dev in zip(self._children, devices):
510 if dev != child.dev_path:
511 base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
512 " RemoveChildren", self.minor, dev, child.dev_path)
514 self._ShutdownLocal(self.minor)
517 def _SetMinorSyncParams(self, minor, params):
518 """Set the parameters of the DRBD syncer.
520 This is the low-level implementation.
523 @param minor: the drbd minor whose settings we change
525 @param params: LD level disk parameters related to the synchronization
527 @return: a list of error messages
530 cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
531 result = utils.RunCmd(cmd)
533 msg = ("Can't change syncer rate: %s - %s" %
534 (result.fail_reason, result.output))
540 def SetSyncParams(self, params):
541 """Set the synchronization parameters of the DRBD syncer.
543 See L{BlockDev.SetSyncParams} for parameter description.
546 if self.minor is None:
547 err = "Not attached during SetSyncParams"
551 children_result = super(DRBD8Dev, self).SetSyncParams(params)
552 children_result.extend(self._SetMinorSyncParams(self.minor, params))
553 return children_result
555 def PauseResumeSync(self, pause):
556 """Pauses or resumes the sync of a DRBD device.
558 See L{BlockDev.PauseResumeSync} for parameter description.
561 if self.minor is None:
562 logging.info("Not attached during PauseSync")
565 children_result = super(DRBD8Dev, self).PauseResumeSync(pause)
568 cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
570 cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
572 result = utils.RunCmd(cmd)
574 logging.error("Can't %s: %s - %s", cmd,
575 result.fail_reason, result.output)
576 return not result.failed and children_result
578 def GetProcStatus(self):
579 """Return the current status data from /proc/drbd for this device.
584 if self.minor is None:
585 base.ThrowError("drbd%d: GetStats() called while not attached",
587 info = DRBD8.GetProcInfo()
588 if not info.HasMinorStatus(self.minor):
589 base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
590 return info.GetMinorStatus(self.minor)
592 def GetSyncStatus(self):
593 """Returns the sync status of the device.
595 If sync_percent is None, it means all is ok
596 If estimated_time is None, it means we can't estimate
597 the time needed, otherwise it's the time left in seconds.
599 We set the is_degraded parameter to True on two conditions:
600 network not connected or local disk missing.
602 We compute the ldisk parameter based on whether we have a local
605 @rtype: objects.BlockDevStatus
608 if self.minor is None and not self.Attach():
609 base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
611 stats = self.GetProcStatus()
612 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
614 if stats.is_disk_uptodate:
615 ldisk_status = constants.LDS_OKAY
616 elif stats.is_diskless:
617 ldisk_status = constants.LDS_FAULTY
619 ldisk_status = constants.LDS_UNKNOWN
621 return objects.BlockDevStatus(dev_path=self.dev_path,
624 sync_percent=stats.sync_percent,
625 estimated_time=stats.est_time,
626 is_degraded=is_degraded,
627 ldisk_status=ldisk_status)
629 def Open(self, force=False):
630 """Make the local state primary.
632 If the 'force' parameter is given, DRBD is instructed to switch the device
633 into primary mode. Since this is a potentially dangerous operation, the
634 force flag should be only given after creation, when it actually is
638 if self.minor is None and not self.Attach():
639 logging.error("DRBD cannot attach to a device during open")
642 cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
644 result = utils.RunCmd(cmd)
646 base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
650 """Make the local state secondary.
652 This will, of course, fail if the device is in use.
655 if self.minor is None and not self.Attach():
656 base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
657 cmd = self._cmd_gen.GenSecondaryCmd(self.minor)
658 result = utils.RunCmd(cmd)
660 base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
661 self.minor, result.output)
663 def DisconnectNet(self):
664 """Removes network configuration.
666 This method shutdowns the network side of the device.
668 The method will wait up to a hardcoded timeout for the device to
669 go into standalone after the 'disconnect' command before
670 re-configuring it, as sometimes it takes a while for the
671 disconnect to actually propagate and thus we might issue a 'net'
672 command while the device is still connected. If the device will
673 still be attached to the network and we time out, we raise an
677 if self.minor is None:
678 base.ThrowError("drbd%d: disk not attached in re-attach net",
681 if None in (self._lhost, self._lport, self._rhost, self._rport):
682 base.ThrowError("drbd%d: DRBD disk missing network info in"
683 " DisconnectNet()", self.minor)
685 class _DisconnectStatus:
686 def __init__(self, ever_disconnected):
687 self.ever_disconnected = ever_disconnected
689 dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
691 def _WaitForDisconnect():
692 if self.GetProcStatus().is_standalone:
695 # retry the disconnect, it seems possible that due to a well-time
696 # disconnect on the peer, my disconnect command might be ignored and
698 dstatus.ever_disconnected = \
699 base.IgnoreError(self._ShutdownNet, self.minor) or \
700 dstatus.ever_disconnected
702 raise utils.RetryAgain()
705 start_time = time.time()
708 # Start delay at 100 milliseconds and grow up to 2 seconds
709 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
710 self._NET_RECONFIG_TIMEOUT)
711 except utils.RetryTimeout:
712 if dstatus.ever_disconnected:
713 msg = ("drbd%d: device did not react to the"
714 " 'disconnect' command in a timely manner")
716 msg = "drbd%d: can't shutdown network, even after multiple retries"
718 base.ThrowError(msg, self.minor)
720 reconfig_time = time.time() - start_time
721 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
722 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
723 self.minor, reconfig_time)
725 def AttachNet(self, multimaster):
726 """Reconnects the network.
728 This method connects the network side of the device with a
729 specified multi-master flag. The device needs to be 'Standalone'
730 but have valid network configuration data.
732 @type multimaster: boolean
733 @param multimaster: init the network in dual-primary mode
736 if self.minor is None:
737 base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
739 if None in (self._lhost, self._lport, self._rhost, self._rport):
740 base.ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
742 status = self.GetProcStatus()
744 if not status.is_standalone:
745 base.ThrowError("drbd%d: device is not standalone in AttachNet",
748 self._AssembleNet(self.minor,
749 (self._lhost, self._lport, self._rhost, self._rport),
750 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
751 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
754 """Check if our minor is configured.
756 This doesn't do any device configurations - it only checks if the
757 minor is in a state different from Unconfigured.
759 Note that this function will not change the state of the system in
760 any way (except in case of side-effects caused by reading from
764 used_devs = DRBD8.GetUsedDevs()
765 if self._aminor in used_devs:
770 self._SetFromMinor(minor)
771 return minor is not None
774 """Assemble the drbd.
777 - if we have a configured device, we try to ensure that it matches
779 - if not, we create it from zero
780 - anyway, set the device parameters
783 super(DRBD8Dev, self).Assemble()
786 if self.minor is None:
787 # local device completely unconfigured
790 # we have to recheck the local and network status and try to fix
794 sync_errors = self.SetSyncParams(self.params)
796 base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
797 (self.minor, utils.CommaJoin(sync_errors)))
799 def _SlowAssemble(self):
800 """Assembles the DRBD device from a (partially) configured device.
802 In case of partially attached (local device matches but no network
803 setup), we perform the network attach. If successful, we re-test
804 the attach if can return success.
807 # TODO: Rewrite to not use a for loop just because there is 'break'
808 # pylint: disable=W0631
809 net_data = (self._lhost, self._lport, self._rhost, self._rport)
810 for minor in (self._aminor,):
811 info = self._GetShowInfo(minor)
812 match_l = self._MatchesLocal(info)
813 match_r = self._MatchesNet(info)
815 if match_l and match_r:
819 if match_l and not match_r and "local_addr" not in info:
820 # disk matches, but not attached to network, attach and recheck
821 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
822 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
823 if self._MatchesNet(self._GetShowInfo(minor)):
826 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
827 " show' disagrees", minor)
829 if match_r and "local_dev" not in info:
830 # no local disk, but network attached and it matches
831 self._AssembleLocal(minor, self._children[0].dev_path,
832 self._children[1].dev_path, self.size)
833 if self._MatchesLocal(self._GetShowInfo(minor)):
836 base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
837 " show' disagrees", minor)
839 # this case must be considered only if we actually have local
840 # storage, i.e. not in diskless mode, because all diskless
841 # devices are equal from the point of view of local
843 if (match_l and "local_dev" in info and
844 not match_r and "local_addr" in info):
845 # strange case - the device network part points to somewhere
846 # else, even though its local storage is ours; as we own the
847 # drbd space, we try to disconnect from the remote peer and
848 # reconnect to our correct one
850 self._ShutdownNet(minor)
851 except errors.BlockDeviceError, err:
852 base.ThrowError("drbd%d: device has correct local storage, wrong"
853 " remote peer and is unable to disconnect in order"
854 " to attach to the correct peer: %s", minor, str(err))
855 # note: _AssembleNet also handles the case when we don't want
856 # local storage (i.e. one or more of the _[lr](host|port) is
858 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
859 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
860 if self._MatchesNet(self._GetShowInfo(minor)):
863 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
864 " show' disagrees", minor)
869 self._SetFromMinor(minor)
871 base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
874 def _FastAssemble(self):
875 """Assemble the drbd device from zero.
877 This is run when in Assemble we detect our minor is unused.
881 if self._children and self._children[0] and self._children[1]:
882 self._AssembleLocal(minor, self._children[0].dev_path,
883 self._children[1].dev_path, self.size)
884 if self._lhost and self._lport and self._rhost and self._rport:
885 self._AssembleNet(minor,
886 (self._lhost, self._lport, self._rhost, self._rport),
887 constants.DRBD_NET_PROTOCOL,
888 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
889 self._SetFromMinor(minor)
891 def _ShutdownLocal(self, minor):
892 """Detach from the local device.
894 I/Os will continue to be served from the remote device. If we
895 don't have a remote device, this operation will fail.
898 @param minor: the device to detach from the local device
901 cmd = self._cmd_gen.GenDetachCmd(minor)
902 result = utils.RunCmd(cmd)
904 base.ThrowError("drbd%d: can't detach local disk: %s",
905 minor, result.output)
907 def _ShutdownNet(self, minor):
908 """Disconnect from the remote peer.
910 This fails if we don't have a local device.
913 @param minor: the device to disconnect from the remote peer
916 family = self._GetNetFamily(minor, self._lhost, self._rhost)
917 cmd = self._cmd_gen.GenDisconnectCmd(minor, family,
918 self._lhost, self._lport,
919 self._rhost, self._rport)
920 result = utils.RunCmd(cmd)
922 base.ThrowError("drbd%d: can't shutdown network: %s",
923 minor, result.output)
926 """Shutdown the DRBD device.
929 if self.minor is None and not self.Attach():
930 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
935 DRBD8.ShutdownAll(minor)
938 """Stub remove for DRBD devices.
943 def Rename(self, new_id):
946 This is not supported for drbd devices.
949 raise errors.ProgrammerError("Can't rename a drbd device")
951 def Grow(self, amount, dryrun, backingstore):
952 """Resize the DRBD device and its backing storage.
954 See L{BlockDev.Grow} for parameter description.
957 if self.minor is None:
958 base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
959 if len(self._children) != 2 or None in self._children:
960 base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
961 self._children[0].Grow(amount, dryrun, backingstore)
962 if dryrun or backingstore:
963 # DRBD does not support dry-run mode and is not backing storage,
964 # so we'll return here
966 cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
967 result = utils.RunCmd(cmd)
969 base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
972 def _InitMeta(cls, minor, dev_path):
973 """Initialize a meta device.
975 This will not work if the given minor is in use.
978 @param minor: the DRBD minor whose (future) meta device should be
980 @type dev_path: string
981 @param dev_path: path to the meta device to initialize
984 # Zero the metadata first, in order to make sure drbdmeta doesn't
985 # try to auto-detect existing filesystems or similar (see
986 # http://code.google.com/p/ganeti/issues/detail?id=182); we only
987 # care about the first 128MB of data in the device, even though it
989 result = utils.RunCmd([constants.DD_CMD,
990 "if=/dev/zero", "of=%s" % dev_path,
991 "bs=1048576", "count=128", "oflag=direct"])
993 base.ThrowError("Can't wipe the meta device: %s", result.output)
995 info = DRBD8.GetProcInfo()
996 cmd_gen = DRBD8.GetCmdGenerator(info)
997 cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
999 result = utils.RunCmd(cmd)
1001 base.ThrowError("Can't initialize meta device: %s", result.output)
1004 def Create(cls, unique_id, children, size, params, excl_stor):
1005 """Create a new DRBD8 device.
1007 Since DRBD devices are not created per se, just assembled, this
1008 function only initializes the metadata.
1011 if len(children) != 2:
1012 raise errors.ProgrammerError("Invalid setup for the drbd device")
1014 raise errors.ProgrammerError("DRBD device requested with"
1015 " exclusive_storage")
1016 # check that the minor is unused
1017 aminor = unique_id[4]
1019 info = DRBD8.GetProcInfo()
1020 if info.HasMinorStatus(aminor):
1021 status = info.GetMinorStatus(aminor)
1022 in_use = status.is_in_use
1026 base.ThrowError("drbd%d: minor is already in use at Create() time",
1030 if not meta.Attach():
1031 base.ThrowError("drbd%d: can't attach to meta device '%s'",
1033 cls._CheckMetaSize(meta.dev_path)
1034 cls._InitMeta(aminor, meta.dev_path)
1035 return cls(unique_id, children, size, params)
1038 def _CanReadDevice(path):
1039 """Check if we can read from the given device.
1041 This tries to read the first 128k of the device.
1047 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1049 except EnvironmentError:
1050 logging.warning("Can't read from device %s", path, exc_info=True)