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 (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.
175 _NET_RECONFIG_TIMEOUT = 60
177 def __init__(self, unique_id, children, size, params, dyn_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 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))
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]
198 if not _CanReadDevice(children[1].dev_path):
199 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
201 super(DRBD8Dev, self).__init__(unique_id, children, size, params,
203 self.major = self._DRBD_MAJOR
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"])
212 if version["k_minor"] <= 3:
213 self._show_info_cls = drbd_info.DRBD83ShowInfo
215 self._show_info_cls = drbd_info.DRBD84ShowInfo
217 self._cmd_gen = DRBD8.GetCmdGenerator(info)
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))
227 """Return the path to a drbd device for a given minor.
233 return "/dev/drbd%d" % minor
235 def _SetFromMinor(self, minor):
236 """Set our parameters based on the given minor.
238 This sets our minor variable and our dev_path.
244 self.minor = self.dev_path = None
245 self.attached = False
248 self.dev_path = self._DevPath(minor)
252 def _CheckMetaSize(meta_device):
253 """Check if the given meta device looks like a valid one.
255 This currently only checks the size, which must be around
258 @type meta_device: string
259 @param meta_device: the path to the device to check
262 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
264 base.ThrowError("Failed to get device size: %s - %s",
265 result.fail_reason, result.output)
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))
284 def _GetShowData(self, minor):
285 """Return the `drbdsetup show` data.
288 @param minor: the minor to collect show output for
292 result = utils.RunCmd(self._cmd_gen.GenShowCmd(minor))
294 logging.error("Can't display the drbd config: %s - %s",
295 result.fail_reason, result.output)
299 def _GetShowInfo(self, minor):
300 """Return parsed information from `drbdsetup show`.
303 @param minor: the minor to return information for
304 @rtype: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
307 return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
309 def _MatchesLocal(self, info):
310 """Test if our local config matches with an existing device.
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
317 @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
322 backend, meta = self._children
324 backend = meta = None
326 if backend is not None:
327 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
329 retval = ("local_dev" not in info)
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
337 retval = retval and ("meta_dev" not in info and
338 "meta_index" not in info)
341 def _MatchesNet(self, info):
342 """Test if our network config matches with an existing device.
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
349 @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
353 if (((self._lhost is None and not ("local_addr" in info)) and
354 (self._rhost is None and not ("remote_addr" in info)))):
357 if self._lhost is None:
360 if not ("local_addr" in info and
361 "remote_addr" in info):
364 retval = (info["local_addr"] == (self._lhost, self._lport))
366 info["remote_addr"] == (self._rhost, self._rport))
369 def _AssembleLocal(self, minor, backend, meta, size):
370 """Configure the local part of a DRBD device.
373 @param minor: the minor to assemble locally
374 @type backend: string
375 @param backend: path to the data device to use
377 @param meta: path to the meta device to use
379 @param size: size in MiB
382 cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta,
386 result = utils.RunCmd(cmd)
388 base.ThrowError("drbd%d: can't attach local disk: %s",
389 minor, result.output)
391 def _AssembleNet(self, minor, net_info, dual_pri=False, hmac=None,
393 """Configure the network part of the device.
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
403 @param hmac: the HMAC algorithm to use
405 @param secret: the shared secret to use
408 lhost, lport, rhost, rport = net_info
410 # we don't want network connection and actually want to make
412 self._ShutdownNet(minor)
416 protocol = constants.DRBD_MIGRATION_NET_PROTOCOL
418 protocol = self.params[constants.LDP_PROTOCOL]
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
426 sync_errors = self._SetMinorSyncParams(minor, self.params)
428 base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
429 (minor, utils.CommaJoin(sync_errors)))
431 family = self._GetNetFamily(minor, lhost, rhost)
433 cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport,
434 rhost, rport, protocol,
435 dual_pri, hmac, secret, self.params)
437 result = utils.RunCmd(cmd)
439 base.ThrowError("drbd%d: can't setup network: %s - %s",
440 minor, result.fail_reason, result.output)
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()
447 if (info["local_addr"] != (lhost, lport) or
448 info["remote_addr"] != (rhost, rport)):
449 raise utils.RetryAgain()
452 utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
453 except utils.RetryTimeout:
454 base.ThrowError("drbd%d: timeout while configuring network", minor)
457 def _GetNetFamily(minor, lhost, rhost):
458 if netutils.IP6Address.IsValid(lhost):
459 if not netutils.IP6Address.IsValid(rhost):
460 base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
461 (minor, lhost, rhost))
463 elif netutils.IP4Address.IsValid(lhost):
464 if not netutils.IP4Address.IsValid(rhost):
465 base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
466 (minor, lhost, rhost))
469 base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
471 def AddChildren(self, devices):
472 """Add a disk to the DRBD device.
474 @type devices: list of L{BlockDev}
475 @param devices: a list of exactly two L{BlockDev} objects; the first
476 denotes the data device, the second the meta device for this DRBD device
479 if self.minor is None:
480 base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
482 if len(devices) != 2:
483 base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
484 info = self._GetShowInfo(self.minor)
485 if "local_dev" in info:
486 base.ThrowError("drbd%d: already attached to a local disk", self.minor)
487 backend, meta = devices
488 if backend.dev_path is None or meta.dev_path is None:
489 base.ThrowError("drbd%d: children not ready during AddChildren",
493 self._CheckMetaSize(meta.dev_path)
494 self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path)
496 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
497 self._children = devices
499 def RemoveChildren(self, devices):
500 """Detach the drbd device from local storage.
502 @type devices: list of L{BlockDev}
503 @param devices: a list of exactly two L{BlockDev} objects; the first
504 denotes the data device, the second the meta device for this DRBD device
507 if self.minor is None:
508 base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
510 # early return if we don't actually have backing storage
511 info = self._GetShowInfo(self.minor)
512 if "local_dev" not in info:
514 if len(self._children) != 2:
515 base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
517 if self._children.count(None) == 2: # we don't actually have children :)
518 logging.warning("drbd%d: requested detach while detached", self.minor)
520 if len(devices) != 2:
521 base.ThrowError("drbd%d: we need two children in RemoveChildren",
523 for child, dev in zip(self._children, devices):
524 if dev != child.dev_path:
525 base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
526 " RemoveChildren", self.minor, dev, child.dev_path)
528 self._ShutdownLocal(self.minor)
531 def _SetMinorSyncParams(self, minor, params):
532 """Set the parameters of the DRBD syncer.
534 This is the low-level implementation.
537 @param minor: the drbd minor whose settings we change
539 @param params: LD level disk parameters related to the synchronization
541 @return: a list of error messages
544 cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
545 result = utils.RunCmd(cmd)
547 msg = ("Can't change syncer rate: %s - %s" %
548 (result.fail_reason, result.output))
554 def SetSyncParams(self, params):
555 """Set the synchronization parameters of the DRBD syncer.
557 See L{BlockDev.SetSyncParams} for parameter description.
560 if self.minor is None:
561 err = "Not attached during SetSyncParams"
565 children_result = super(DRBD8Dev, self).SetSyncParams(params)
566 children_result.extend(self._SetMinorSyncParams(self.minor, params))
567 return children_result
569 def PauseResumeSync(self, pause):
570 """Pauses or resumes the sync of a DRBD device.
572 See L{BlockDev.PauseResumeSync} for parameter description.
575 if self.minor is None:
576 logging.info("Not attached during PauseSync")
579 children_result = super(DRBD8Dev, self).PauseResumeSync(pause)
582 cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
584 cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
586 result = utils.RunCmd(cmd)
588 logging.error("Can't %s: %s - %s", cmd,
589 result.fail_reason, result.output)
590 return not result.failed and children_result
592 def GetProcStatus(self):
593 """Return the current status data from /proc/drbd for this device.
598 if self.minor is None:
599 base.ThrowError("drbd%d: GetStats() called while not attached",
601 info = DRBD8.GetProcInfo()
602 if not info.HasMinorStatus(self.minor):
603 base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
604 return info.GetMinorStatus(self.minor)
606 def GetSyncStatus(self):
607 """Returns the sync status of the device.
609 If sync_percent is None, it means all is ok
610 If estimated_time is None, it means we can't estimate
611 the time needed, otherwise it's the time left in seconds.
613 We set the is_degraded parameter to True on two conditions:
614 network not connected or local disk missing.
616 We compute the ldisk parameter based on whether we have a local
619 @rtype: objects.BlockDevStatus
622 if self.minor is None and not self.Attach():
623 base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
625 stats = self.GetProcStatus()
626 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
628 if stats.is_disk_uptodate:
629 ldisk_status = constants.LDS_OKAY
630 elif stats.is_diskless:
631 ldisk_status = constants.LDS_FAULTY
633 ldisk_status = constants.LDS_UNKNOWN
635 return objects.BlockDevStatus(dev_path=self.dev_path,
638 sync_percent=stats.sync_percent,
639 estimated_time=stats.est_time,
640 is_degraded=is_degraded,
641 ldisk_status=ldisk_status)
643 def Open(self, force=False):
644 """Make the local state primary.
646 If the 'force' parameter is given, DRBD is instructed to switch the device
647 into primary mode. Since this is a potentially dangerous operation, the
648 force flag should be only given after creation, when it actually is
652 if self.minor is None and not self.Attach():
653 logging.error("DRBD cannot attach to a device during open")
656 cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
658 result = utils.RunCmd(cmd)
660 base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
664 """Make the local state secondary.
666 This will, of course, fail if the device is in use.
669 if self.minor is None and not self.Attach():
670 base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
671 cmd = self._cmd_gen.GenSecondaryCmd(self.minor)
672 result = utils.RunCmd(cmd)
674 base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
675 self.minor, result.output)
677 def DisconnectNet(self):
678 """Removes network configuration.
680 This method shutdowns the network side of the device.
682 The method will wait up to a hardcoded timeout for the device to
683 go into standalone after the 'disconnect' command before
684 re-configuring it, as sometimes it takes a while for the
685 disconnect to actually propagate and thus we might issue a 'net'
686 command while the device is still connected. If the device will
687 still be attached to the network and we time out, we raise an
691 if self.minor is None:
692 base.ThrowError("drbd%d: disk not attached in re-attach net",
695 if None in (self._lhost, self._lport, self._rhost, self._rport):
696 base.ThrowError("drbd%d: DRBD disk missing network info in"
697 " DisconnectNet()", self.minor)
699 class _DisconnectStatus:
700 def __init__(self, ever_disconnected):
701 self.ever_disconnected = ever_disconnected
703 dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
705 def _WaitForDisconnect():
706 if self.GetProcStatus().is_standalone:
709 # retry the disconnect, it seems possible that due to a well-time
710 # disconnect on the peer, my disconnect command might be ignored and
712 dstatus.ever_disconnected = \
713 base.IgnoreError(self._ShutdownNet, self.minor) or \
714 dstatus.ever_disconnected
716 raise utils.RetryAgain()
719 start_time = time.time()
722 # Start delay at 100 milliseconds and grow up to 2 seconds
723 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
724 self._NET_RECONFIG_TIMEOUT)
725 except utils.RetryTimeout:
726 if dstatus.ever_disconnected:
727 msg = ("drbd%d: device did not react to the"
728 " 'disconnect' command in a timely manner")
730 msg = "drbd%d: can't shutdown network, even after multiple retries"
732 base.ThrowError(msg, self.minor)
734 reconfig_time = time.time() - start_time
735 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
736 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
737 self.minor, reconfig_time)
739 def AttachNet(self, multimaster):
740 """Reconnects the network.
742 This method connects the network side of the device with a
743 specified multi-master flag. The device needs to be 'Standalone'
744 but have valid network configuration data.
746 @type multimaster: boolean
747 @param multimaster: init the network in dual-primary mode
750 if self.minor is None:
751 base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
753 if None in (self._lhost, self._lport, self._rhost, self._rport):
754 base.ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
756 status = self.GetProcStatus()
758 if not status.is_standalone:
759 base.ThrowError("drbd%d: device is not standalone in AttachNet",
762 self._AssembleNet(self.minor,
763 (self._lhost, self._lport, self._rhost, self._rport),
764 dual_pri=multimaster, hmac=constants.DRBD_HMAC_ALG,
768 """Check if our minor is configured.
770 This doesn't do any device configurations - it only checks if the
771 minor is in a state different from Unconfigured.
773 Note that this function will not change the state of the system in
774 any way (except in case of side-effects caused by reading from
778 used_devs = DRBD8.GetUsedDevs()
779 if self._aminor in used_devs:
784 self._SetFromMinor(minor)
785 return minor is not None
788 """Assemble the drbd.
791 - if we have a configured device, we try to ensure that it matches
793 - if not, we create it from zero
794 - anyway, set the device parameters
797 super(DRBD8Dev, self).Assemble()
800 if self.minor is None:
801 # local device completely unconfigured
804 # we have to recheck the local and network status and try to fix
808 sync_errors = self.SetSyncParams(self.params)
810 base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
811 (self.minor, utils.CommaJoin(sync_errors)))
813 def _SlowAssemble(self):
814 """Assembles the DRBD device from a (partially) configured device.
816 In case of partially attached (local device matches but no network
817 setup), we perform the network attach. If successful, we re-test
818 the attach if can return success.
821 # TODO: Rewrite to not use a for loop just because there is 'break'
822 # pylint: disable=W0631
823 net_data = (self._lhost, self._lport, self._rhost, self._rport)
824 for minor in (self._aminor,):
825 info = self._GetShowInfo(minor)
826 match_l = self._MatchesLocal(info)
827 match_r = self._MatchesNet(info)
829 if match_l and match_r:
833 if match_l and not match_r and "local_addr" not in info:
834 # disk matches, but not attached to network, attach and recheck
835 self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG,
837 if self._MatchesNet(self._GetShowInfo(minor)):
840 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
841 " show' disagrees", minor)
843 if match_r and "local_dev" not in info:
844 # no local disk, but network attached and it matches
845 self._AssembleLocal(minor, self._children[0].dev_path,
846 self._children[1].dev_path, self.size)
847 if self._MatchesLocal(self._GetShowInfo(minor)):
850 base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
851 " show' disagrees", minor)
853 # this case must be considered only if we actually have local
854 # storage, i.e. not in diskless mode, because all diskless
855 # devices are equal from the point of view of local
857 if (match_l and "local_dev" in info and
858 not match_r and "local_addr" in info):
859 # strange case - the device network part points to somewhere
860 # else, even though its local storage is ours; as we own the
861 # drbd space, we try to disconnect from the remote peer and
862 # reconnect to our correct one
864 self._ShutdownNet(minor)
865 except errors.BlockDeviceError, err:
866 base.ThrowError("drbd%d: device has correct local storage, wrong"
867 " remote peer and is unable to disconnect in order"
868 " to attach to the correct peer: %s", minor, str(err))
869 # note: _AssembleNet also handles the case when we don't want
870 # local storage (i.e. one or more of the _[lr](host|port) is
872 self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG,
874 if self._MatchesNet(self._GetShowInfo(minor)):
877 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
878 " show' disagrees", minor)
883 self._SetFromMinor(minor)
885 base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
888 def _FastAssemble(self):
889 """Assemble the drbd device from zero.
891 This is run when in Assemble we detect our minor is unused.
895 if self._children and self._children[0] and self._children[1]:
896 self._AssembleLocal(minor, self._children[0].dev_path,
897 self._children[1].dev_path, self.size)
898 if self._lhost and self._lport and self._rhost and self._rport:
899 self._AssembleNet(minor,
900 (self._lhost, self._lport, self._rhost, self._rport),
901 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
902 self._SetFromMinor(minor)
904 def _ShutdownLocal(self, minor):
905 """Detach from the local device.
907 I/Os will continue to be served from the remote device. If we
908 don't have a remote device, this operation will fail.
911 @param minor: the device to detach from the local device
914 cmd = self._cmd_gen.GenDetachCmd(minor)
915 result = utils.RunCmd(cmd)
917 base.ThrowError("drbd%d: can't detach local disk: %s",
918 minor, result.output)
920 def _ShutdownNet(self, minor):
921 """Disconnect from the remote peer.
923 This fails if we don't have a local device.
926 @param minor: the device to disconnect from the remote peer
929 family = self._GetNetFamily(minor, self._lhost, self._rhost)
930 cmd = self._cmd_gen.GenDisconnectCmd(minor, family,
931 self._lhost, self._lport,
932 self._rhost, self._rport)
933 result = utils.RunCmd(cmd)
935 base.ThrowError("drbd%d: can't shutdown network: %s",
936 minor, result.output)
939 """Shutdown the DRBD device.
942 if self.minor is None and not self.Attach():
943 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
947 DRBD8.ShutdownAll(self.minor)
953 """Stub remove for DRBD devices.
958 def Rename(self, new_id):
961 This is not supported for drbd devices.
964 raise errors.ProgrammerError("Can't rename a drbd device")
966 def Grow(self, amount, dryrun, backingstore, excl_stor):
967 """Resize the DRBD device and its backing storage.
969 See L{BlockDev.Grow} for parameter description.
972 if self.minor is None:
973 base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
974 if len(self._children) != 2 or None in self._children:
975 base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
976 self._children[0].Grow(amount, dryrun, backingstore, excl_stor)
977 if dryrun or backingstore:
978 # DRBD does not support dry-run mode and is not backing storage,
979 # so we'll return here
981 cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
982 result = utils.RunCmd(cmd)
984 base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
987 def _InitMeta(cls, minor, dev_path):
988 """Initialize a meta device.
990 This will not work if the given minor is in use.
993 @param minor: the DRBD minor whose (future) meta device should be
995 @type dev_path: string
996 @param dev_path: path to the meta device to initialize
999 # Zero the metadata first, in order to make sure drbdmeta doesn't
1000 # try to auto-detect existing filesystems or similar (see
1001 # http://code.google.com/p/ganeti/issues/detail?id=182); we only
1002 # care about the first 128MB of data in the device, even though it
1004 result = utils.RunCmd([constants.DD_CMD,
1005 "if=/dev/zero", "of=%s" % dev_path,
1006 "bs=1048576", "count=128", "oflag=direct"])
1008 base.ThrowError("Can't wipe the meta device: %s", result.output)
1010 info = DRBD8.GetProcInfo()
1011 cmd_gen = DRBD8.GetCmdGenerator(info)
1012 cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
1014 result = utils.RunCmd(cmd)
1016 base.ThrowError("Can't initialize meta device: %s", result.output)
1019 def Create(cls, unique_id, children, size, spindles, params, excl_stor,
1021 """Create a new DRBD8 device.
1023 Since DRBD devices are not created per se, just assembled, this
1024 function only initializes the metadata.
1027 if len(children) != 2:
1028 raise errors.ProgrammerError("Invalid setup for the drbd device")
1030 raise errors.ProgrammerError("DRBD device requested with"
1031 " exclusive_storage")
1032 if constants.DDP_LOCAL_MINOR not in dyn_params:
1033 raise errors.ProgrammerError("Invalid dynamic params for drbd device %s"
1035 # check that the minor is unused
1036 aminor = dyn_params[constants.DDP_LOCAL_MINOR]
1038 info = DRBD8.GetProcInfo()
1039 if info.HasMinorStatus(aminor):
1040 status = info.GetMinorStatus(aminor)
1041 in_use = status.is_in_use
1045 base.ThrowError("drbd%d: minor is already in use at Create() time",
1049 if not meta.Attach():
1050 base.ThrowError("drbd%d: can't attach to meta device '%s'",
1052 cls._CheckMetaSize(meta.dev_path)
1053 cls._InitMeta(aminor, meta.dev_path)
1054 return cls(unique_id, children, size, params, dyn_params)
1057 def _CanReadDevice(path):
1058 """Check if we can read from the given device.
1060 This tries to read the first 128k of the device.
1066 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1068 except EnvironmentError:
1069 logging.warning("Can't read from device %s", path, exc_info=True)