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, *args):
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)
456 # Once the assembly is over, try to set the synchronization parameters
458 # The minor may not have been set yet, requiring us to set it at least
460 old_minor = self.minor
461 self._SetFromMinor(minor)
462 sync_errors = self.SetSyncParams(self.params)
464 base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
465 (self.minor, utils.CommaJoin(sync_errors)))
467 # Undo the change, regardless of whether it will have to be done again
469 self._SetFromMinor(old_minor)
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))
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))
484 base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
486 def AddChildren(self, devices):
487 """Add a disk to the DRBD device.
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
494 if self.minor is None:
495 base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
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",
508 self._CheckMetaSize(meta.dev_path)
509 self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path)
511 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
512 self._children = devices
514 def RemoveChildren(self, devices):
515 """Detach the drbd device from local storage.
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
522 if self.minor is None:
523 base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
525 # early return if we don't actually have backing storage
526 info = self._GetShowInfo(self.minor)
527 if "local_dev" not in info:
529 if len(self._children) != 2:
530 base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
532 if self._children.count(None) == 2: # we don't actually have children :)
533 logging.warning("drbd%d: requested detach while detached", self.minor)
535 if len(devices) != 2:
536 base.ThrowError("drbd%d: we need two children in RemoveChildren",
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)
543 self._ShutdownLocal(self.minor)
546 def _SetMinorSyncParams(self, minor, params):
547 """Set the parameters of the DRBD syncer.
549 This is the low-level implementation.
552 @param minor: the drbd minor whose settings we change
554 @param params: LD level disk parameters related to the synchronization
556 @return: a list of error messages
559 cmd = self._cmd_gen.GenSyncParamsCmd(minor, params)
560 result = utils.RunCmd(cmd)
562 msg = ("Can't change syncer rate: %s - %s" %
563 (result.fail_reason, result.output))
569 def SetSyncParams(self, params):
570 """Set the synchronization parameters of the DRBD syncer.
572 See L{BlockDev.SetSyncParams} for parameter description.
575 if self.minor is None:
576 err = "Not attached during SetSyncParams"
580 children_result = super(DRBD8Dev, self).SetSyncParams(params)
581 children_result.extend(self._SetMinorSyncParams(self.minor, params))
582 return children_result
584 def PauseResumeSync(self, pause):
585 """Pauses or resumes the sync of a DRBD device.
587 See L{BlockDev.PauseResumeSync} for parameter description.
590 if self.minor is None:
591 logging.info("Not attached during PauseSync")
594 children_result = super(DRBD8Dev, self).PauseResumeSync(pause)
597 cmd = self._cmd_gen.GenPauseSyncCmd(self.minor)
599 cmd = self._cmd_gen.GenResumeSyncCmd(self.minor)
601 result = utils.RunCmd(cmd)
603 logging.error("Can't %s: %s - %s", cmd,
604 result.fail_reason, result.output)
605 return not result.failed and children_result
607 def GetProcStatus(self):
608 """Return the current status data from /proc/drbd for this device.
613 if self.minor is None:
614 base.ThrowError("drbd%d: GetStats() called while not attached",
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)
621 def GetSyncStatus(self):
622 """Returns the sync status of the device.
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.
628 We set the is_degraded parameter to True on two conditions:
629 network not connected or local disk missing.
631 We compute the ldisk parameter based on whether we have a local
634 @rtype: objects.BlockDevStatus
637 if self.minor is None and not self.Attach():
638 base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
640 stats = self.GetProcStatus()
641 is_degraded = not stats.is_connected or not stats.is_disk_uptodate
643 if stats.is_disk_uptodate:
644 ldisk_status = constants.LDS_OKAY
645 elif stats.is_diskless:
646 ldisk_status = constants.LDS_FAULTY
648 ldisk_status = constants.LDS_UNKNOWN
650 return objects.BlockDevStatus(dev_path=self.dev_path,
653 sync_percent=stats.sync_percent,
654 estimated_time=stats.est_time,
655 is_degraded=is_degraded,
656 ldisk_status=ldisk_status)
658 def Open(self, force=False):
659 """Make the local state primary.
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
667 if self.minor is None and not self.Attach():
668 logging.error("DRBD cannot attach to a device during open")
671 cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force)
673 result = utils.RunCmd(cmd)
675 base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
679 """Make the local state secondary.
681 This will, of course, fail if the device is in use.
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)
689 base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
690 self.minor, result.output)
692 def DisconnectNet(self):
693 """Removes network configuration.
695 This method shutdowns the network side of the device.
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
706 if self.minor is None:
707 base.ThrowError("drbd%d: disk not attached in re-attach net",
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)
714 class _DisconnectStatus:
715 def __init__(self, ever_disconnected):
716 self.ever_disconnected = ever_disconnected
718 dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
720 def _WaitForDisconnect():
721 if self.GetProcStatus().is_standalone:
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
727 dstatus.ever_disconnected = \
728 base.IgnoreError(self._ShutdownNet, self.minor) or \
729 dstatus.ever_disconnected
731 raise utils.RetryAgain()
734 start_time = time.time()
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")
745 msg = "drbd%d: can't shutdown network, even after multiple retries"
747 base.ThrowError(msg, self.minor)
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)
754 def AttachNet(self, multimaster):
755 """Reconnects the network.
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.
761 @type multimaster: boolean
762 @param multimaster: init the network in dual-primary mode
765 if self.minor is None:
766 base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
768 if None in (self._lhost, self._lport, self._rhost, self._rport):
769 base.ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
771 status = self.GetProcStatus()
773 if not status.is_standalone:
774 base.ThrowError("drbd%d: device is not standalone in AttachNet",
777 self._AssembleNet(self.minor,
778 (self._lhost, self._lport, self._rhost, self._rport),
779 dual_pri=multimaster, hmac=constants.DRBD_HMAC_ALG,
783 """Check if our minor is configured.
785 This doesn't do any device configurations - it only checks if the
786 minor is in a state different from Unconfigured.
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
793 used_devs = DRBD8.GetUsedDevs()
794 if self._aminor in used_devs:
799 self._SetFromMinor(minor)
800 return minor is not None
803 """Assemble the drbd.
806 - if we have a configured device, we try to ensure that it matches
808 - if not, we create it from zero
809 - anyway, set the device parameters
812 super(DRBD8Dev, self).Assemble()
815 if self.minor is None:
816 # local device completely unconfigured
819 # we have to recheck the local and network status and try to fix
823 def _SlowAssemble(self):
824 """Assembles the DRBD device from a (partially) configured device.
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.
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)
839 if match_l and match_r:
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,
847 if self._MatchesNet(self._GetShowInfo(minor)):
850 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
851 " show' disagrees", minor)
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)):
860 base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
861 " show' disagrees", minor)
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
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
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
882 self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG,
884 if self._MatchesNet(self._GetShowInfo(minor)):
887 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
888 " show' disagrees", minor)
893 self._SetFromMinor(minor)
895 base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
898 def _FastAssemble(self):
899 """Assemble the drbd device from zero.
901 This is run when in Assemble we detect our minor is unused.
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)
914 def _ShutdownLocal(self, minor):
915 """Detach from the local device.
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.
921 @param minor: the device to detach from the local device
924 cmd = self._cmd_gen.GenDetachCmd(minor)
925 result = utils.RunCmd(cmd)
927 base.ThrowError("drbd%d: can't detach local disk: %s",
928 minor, result.output)
930 def _ShutdownNet(self, minor):
931 """Disconnect from the remote peer.
933 This fails if we don't have a local device.
936 @param minor: the device to disconnect from the remote peer
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)
945 base.ThrowError("drbd%d: can't shutdown network: %s",
946 minor, result.output)
949 """Shutdown the DRBD device.
952 if self.minor is None and not self.Attach():
953 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
957 DRBD8.ShutdownAll(self.minor)
963 """Stub remove for DRBD devices.
968 def Rename(self, new_id):
971 This is not supported for drbd devices.
974 raise errors.ProgrammerError("Can't rename a drbd device")
976 def Grow(self, amount, dryrun, backingstore, excl_stor):
977 """Resize the DRBD device and its backing storage.
979 See L{BlockDev.Grow} for parameter description.
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
991 cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
992 result = utils.RunCmd(cmd)
994 base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
997 def _InitMeta(cls, minor, dev_path):
998 """Initialize a meta device.
1000 This will not work if the given minor is in use.
1003 @param minor: the DRBD minor whose (future) meta device should be
1005 @type dev_path: string
1006 @param dev_path: path to the meta device to initialize
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
1014 result = utils.RunCmd([constants.DD_CMD,
1015 "if=/dev/zero", "of=%s" % dev_path,
1016 "bs=1048576", "count=128", "oflag=direct"])
1018 base.ThrowError("Can't wipe the meta device: %s", result.output)
1020 info = DRBD8.GetProcInfo()
1021 cmd_gen = DRBD8.GetCmdGenerator(info)
1022 cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
1024 result = utils.RunCmd(cmd)
1026 base.ThrowError("Can't initialize meta device: %s", result.output)
1029 def Create(cls, unique_id, children, size, spindles, params, excl_stor,
1031 """Create a new DRBD8 device.
1033 Since DRBD devices are not created per se, just assembled, this
1034 function only initializes the metadata.
1037 if len(children) != 2:
1038 raise errors.ProgrammerError("Invalid setup for the drbd device")
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"
1045 # check that the minor is unused
1046 aminor = dyn_params[constants.DDP_LOCAL_MINOR]
1048 info = DRBD8.GetProcInfo()
1049 if info.HasMinorStatus(aminor):
1050 status = info.GetMinorStatus(aminor)
1051 in_use = status.is_in_use
1055 base.ThrowError("drbd%d: minor is already in use at Create() time",
1059 if not meta.Attach():
1060 base.ThrowError("drbd%d: can't attach to meta device '%s'",
1062 cls._CheckMetaSize(meta.dev_path)
1063 cls._InitMeta(aminor, meta.dev_path)
1064 return cls(unique_id, children, size, params, dyn_params)
1067 def _CanReadDevice(path):
1068 """Check if we can read from the given device.
1070 This tries to read the first 128k of the device.
1076 utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1078 except EnvironmentError:
1079 logging.warning("Can't read from device %s", path, exc_info=True)