diff --git a/lib/block/drbd.py b/lib/block/drbd.py index 3df7db799777d21fb666d08157a42358f1ee52c9..b69a82197a772c97fd1fa0dfe0c516de813a8021 100644 --- a/lib/block/drbd.py +++ b/lib/block/drbd.py @@ -90,11 +90,10 @@ class DRBD8(base.BlockDev): if version["k_minor"] <= 3: self._show_info_cls = DRBD83ShowInfo - self._cmd_gen = drbd_cmdgen.DRBD83CmdGenerator(drbd_info) else: self._show_info_cls = DRBD84ShowInfo - # FIXME: use proper command generator! - self._cmd_gen = None + + self._cmd_gen = self._GetCmdGenerator(drbd_info) if (self._lhost is not None and self._lhost == self._rhost and self._lport == self._rport): @@ -102,6 +101,14 @@ class DRBD8(base.BlockDev): (unique_id,)) self.Attach() + @classmethod + def _GetCmdGenerator(cls, drbd_info): + version = drbd_info.GetVersion() + if version["k_minor"] <= 3: + return drbd_cmdgen.DRBD83CmdGenerator(version) + else: + return drbd_cmdgen.DRBD84CmdGenerator(version) + @staticmethod def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE): """Returns DRBD usermode_helper currently set. @@ -198,8 +205,11 @@ class DRBD8(base.BlockDev): if result.failed: base.ThrowError("Can't wipe the meta device: %s", result.output) - result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor), - "v08", dev_path, "0", "create-md"]) + drbd_info = DRBD8Info.CreateFromFile() + cmd_gen = cls._GetCmdGenerator(drbd_info) + cmd = cmd_gen.GenInitMetaCmd(minor, dev_path) + + result = utils.RunCmd(cmd) if result.failed: base.ThrowError("Can't initialize meta device: %s", result.output) @@ -331,18 +341,7 @@ class DRBD8(base.BlockDev): base.ThrowError("drbd%d: can't set the synchronization parameters: %s" % (minor, utils.CommaJoin(sync_errors))) - if netutils.IP6Address.IsValid(lhost): - if not netutils.IP6Address.IsValid(rhost): - base.ThrowError("drbd%d: can't connect ip %s to ip %s" % - (minor, lhost, rhost)) - family = "ipv6" - elif netutils.IP4Address.IsValid(lhost): - if not netutils.IP4Address.IsValid(rhost): - base.ThrowError("drbd%d: can't connect ip %s to ip %s" % - (minor, lhost, rhost)) - family = "ipv4" - else: - base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost)) + family = self._GetNetFamily(minor, lhost, rhost) cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport, rhost, rport, protocol, @@ -367,6 +366,21 @@ class DRBD8(base.BlockDev): except utils.RetryTimeout: base.ThrowError("drbd%d: timeout while configuring network", minor) + @staticmethod + def _GetNetFamily(minor, lhost, rhost): + if netutils.IP6Address.IsValid(lhost): + if not netutils.IP6Address.IsValid(rhost): + base.ThrowError("drbd%d: can't connect ip %s to ip %s" % + (minor, lhost, rhost)) + return "ipv6" + elif netutils.IP4Address.IsValid(lhost): + if not netutils.IP4Address.IsValid(rhost): + base.ThrowError("drbd%d: can't connect ip %s to ip %s" % + (minor, lhost, rhost)) + return "ipv4" + else: + base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost)) + def AddChildren(self, devices): """Add a disk to the DRBD device. @@ -817,7 +831,10 @@ class DRBD8(base.BlockDev): This fails if we don't have a local device. """ - cmd = self._cmd_gen.GenDisconnectCmd(minor) + family = self._GetNetFamily(minor, self._lhost, self._rhost) + cmd = self._cmd_gen.GenDisconnectCmd(minor, family, + self._lhost, self._lport, + self._rhost, self._rport) result = utils.RunCmd(cmd) if result.failed: base.ThrowError("drbd%d: can't shutdown network: %s", @@ -832,13 +849,9 @@ class DRBD8(base.BlockDev): """ # FIXME: _ShutdownAll, despite being private, is used in nodemaint.py. # That's why we can't make it an instance method, which in turn requires - # us to duplicate code here (from __init__). This should be proberly fixed. + # us to duplicate code here (from __init__). This should be properly fixed. drbd_info = DRBD8Info.CreateFromFile() - if drbd_info.GetVersion()["k_minor"] <= 3: - cmd_gen = drbd_cmdgen.DRBD83CmdGenerator(drbd_info) - else: - # FIXME: use proper command generator! - cmd_gen = None + cmd_gen = cls._GetCmdGenerator(drbd_info) cmd = cmd_gen.GenDownCmd(minor) result = utils.RunCmd(cmd) diff --git a/lib/block/drbd_cmdgen.py b/lib/block/drbd_cmdgen.py index 0877279924776fdc6e107027e0b5de31fc511b7a..35ee8755ef8d978b9e354b83cfdd9606b45d114a 100644 --- a/lib/block/drbd_cmdgen.py +++ b/lib/block/drbd_cmdgen.py @@ -28,8 +28,66 @@ from ganeti import constants from ganeti import errors -class DRBD83CmdGenerator(object): - """Generates drbdsetup commands suited for the DRBD <= 8.3 syntax +class BaseDRBDCmdGenerator(object): + """Base class for DRBD command generators. + + This class defines the interface for the command generators and holds shared + code. + + """ + def __init__(self, version): + self._version = version + + def GenShowCmd(self, minor): + raise NotImplementedError + + def GenInitMetaCmd(self, minor, meta_dev): + raise NotImplementedError + + def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params): + raise NotImplementedError + + def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol, + dual_pri, hmac, secret, params): + raise NotImplementedError + + def GenSyncParamsCmd(self, minor, params): + raise NotImplementedError + + def GenPauseSyncCmd(self, minor): + raise NotImplementedError + + def GenResumeSyncCmd(self, minor): + raise NotImplementedError + + def GenPrimaryCmd(self, minor, force): + raise NotImplementedError + + def GenSecondaryCmd(self, minor): + raise NotImplementedError + + def GenDetachCmd(self, minor): + raise NotImplementedError + + def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport): + raise NotImplementedError + + def GenDownCmd(self, minor): + raise NotImplementedError + + def GenResizeCmd(self, minor, size_mb): + raise NotImplementedError + + @staticmethod + def _DevPath(minor): + """Return the path to a drbd device for a given minor. + + """ + return "/dev/drbd%d" % minor + + +class DRBD83CmdGenerator(BaseDRBDCmdGenerator): + """Generates drbdsetup commands suited for the DRBD <= 8.3 syntax. """ # command line options for barriers @@ -38,12 +96,16 @@ class DRBD83CmdGenerator(object): _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i _DISABLE_META_FLUSH_OPTION = "--no-md-flushes" # -m - def __init__(self, drbd_info): - self._drbd_info = drbd_info + def __init__(self, version): + super(DRBD83CmdGenerator, self).__init__(version) def GenShowCmd(self, minor): return ["drbdsetup", self._DevPath(minor), "show"] + def GenInitMetaCmd(self, minor, meta_dev): + return ["drbdmeta", "--force", self._DevPath(minor), + "v08", meta_dev, "0", "create-md"] + def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params): args = ["drbdsetup", self._DevPath(minor), "disk", data_dev, meta_dev, "0", @@ -52,10 +114,9 @@ class DRBD83CmdGenerator(object): if size_mb: args.extend(["-d", "%sm" % size_mb]) - version = self._drbd_info.GetVersion() - vmaj = version["k_major"] - vmin = version["k_minor"] - vrel = version["k_point"] + vmaj = self._version["k_major"] + vmin = self._version["k_minor"] + vrel = self._version["k_point"] barrier_args = \ self._ComputeDiskBarrierArgs(vmaj, vmin, vrel, @@ -90,9 +151,8 @@ class DRBD83CmdGenerator(object): def GenSyncParamsCmd(self, minor, params): args = ["drbdsetup", self._DevPath(minor), "syncer"] if params[constants.LDP_DYNAMIC_RESYNC]: - version = self._drbd_info.GetVersion() - vmin = version["k_minor"] - vrel = version["k_point"] + vmin = self._version["k_minor"] + vrel = self._version["k_point"] # By definition we are using 8.x, so just check the rest of the version # number @@ -144,7 +204,7 @@ class DRBD83CmdGenerator(object): def GenDetachCmd(self, minor): return ["drbdsetup", self._DevPath(minor), "detach"] - def GenDisconnectCmd(self, minor): + def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport): return ["drbdsetup", self._DevPath(minor), "disconnect"] def GenDownCmd(self, minor): @@ -153,13 +213,6 @@ class DRBD83CmdGenerator(object): def GenResizeCmd(self, minor, size_mb): return ["drbdsetup", self._DevPath(minor), "resize", "-s", "%dm" % size_mb] - @staticmethod - def _DevPath(minor): - """Return the path to a drbd device for a given minor. - - """ - return "/dev/drbd%d" % minor - @classmethod def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers, disable_meta_flush): @@ -233,3 +286,166 @@ class DRBD83CmdGenerator(object): disk_barriers_supported.get(vmin, None)) return args + + +class DRBD84CmdGenerator(BaseDRBDCmdGenerator): + """Generates drbdsetup commands suited for the DRBD >= 8.4 syntax. + + """ + # command line options for barriers + _DISABLE_DISK_OPTION = "--disk-barrier=no" + _DISABLE_DRAIN_OPTION = "--disk-drain=no" + _DISABLE_FLUSH_OPTION = "--disk-flushes=no" + _DISABLE_META_FLUSH_OPTION = "--md-flushes=no" + + def __init__(self, version): + super(DRBD84CmdGenerator, self).__init__(version) + + def GenShowCmd(self, minor): + return ["drbdsetup", "show", minor] + + def GenInitMetaCmd(self, minor, meta_dev): + return ["drbdmeta", "--force", self._DevPath(minor), + "v08", meta_dev, "0", "create-md"] + + def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params): + cmds = [] + + cmds.append(["drbdsetup", "new-resource", self._GetResource(minor)]) + cmds.append(["drbdsetup", "new-minor", self._GetResource(minor), + str(minor), "0"]) + # We need to apply the activity log before attaching the disk else drbdsetup + # will fail. + cmds.append(["drbdmeta", self._DevPath(minor), + "v08", meta_dev, "0", "apply-al"]) + + attach_cmd = ["drbdsetup", "attach", minor, data_dev, meta_dev, "0", + "--on-io-error=detach"] + if size_mb: + attach_cmd.extend(["--size", "%sm" % size_mb]) + + barrier_args = \ + self._ComputeDiskBarrierArgs(params[constants.LDP_BARRIERS], + params[constants.LDP_NO_META_FLUSH]) + attach_cmd.extend(barrier_args) + + if params[constants.LDP_DISK_CUSTOM]: + attach_cmd.extend(shlex.split(params[constants.LDP_DISK_CUSTOM])) + + cmds.append(attach_cmd) + + return cmds + + def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol, + dual_pri, hmac, secret, params): + args = ["drbdsetup", "connect", self._GetResource(minor), + "%s:%s:%s" % (family, lhost, lport), + "%s:%s:%s" % (family, rhost, rport), + "--protocol", protocol, + "--after-sb-0pri", "discard-zero-changes", + "--after-sb-1pri", "consensus" + ] + if dual_pri: + args.append("--allow-two-primaries") + if hmac and secret: + args.extend(["--cram-hmac-alg", hmac, "--shared-secret", secret]) + + if params[constants.LDP_NET_CUSTOM]: + args.extend(shlex.split(params[constants.LDP_NET_CUSTOM])) + + return args + + def GenSyncParamsCmd(self, minor, params): + args = ["drbdsetup", "disk-options", minor] + if params[constants.LDP_DYNAMIC_RESYNC]: + if params[constants.LDP_PLAN_AHEAD] == 0: + msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed" + " controller at DRBD level. If you want to disable it, please" + " set the dynamic-resync disk parameter to False.") + logging.error(msg) + return [msg] + + # add the c-* parameters to args + args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD], + "--c-fill-target", params[constants.LDP_FILL_TARGET], + "--c-delay-target", params[constants.LDP_DELAY_TARGET], + "--c-max-rate", params[constants.LDP_MAX_RATE], + "--c-min-rate", params[constants.LDP_MIN_RATE], + ]) + + else: + args.extend(["--resync-rate", "%d" % params[constants.LDP_RESYNC_RATE]]) + + return args + + def GenPauseSyncCmd(self, minor): + return ["drbdsetup", "pause-sync", minor] + + def GenResumeSyncCmd(self, minor): + return ["drbdsetup", "resume-sync", minor] + + def GenPrimaryCmd(self, minor, force): + cmd = ["drbdsetup", "primary", minor] + + if force: + cmd.append("--force") + + return cmd + + def GenSecondaryCmd(self, minor): + return ["drbdsetup", "secondary", minor] + + def GenDetachCmd(self, minor): + return ["drbdsetup", "detach", minor] + + def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport): + return ["drbdsetup", "disconnect", + "%s:%s:%s" % (family, lhost, lport), + "%s:%s:%s" % (family, rhost, rport)] + + def GenDownCmd(self, minor): + return ["drbdsetup", "down", self._GetResource(minor)] + + def GenResizeCmd(self, minor, size_mb): + return ["drbdsetup", "resize", minor, "--size", "%dm" % size_mb] + + @staticmethod + def _GetResource(minor): + """Return the resource name for a given minor. + + Currently we don't support DRBD volumes which share a resource, so we + generate the resource name based on the minor the resulting volumes is + assigned to. + + """ + return "resource%d" % minor + + @classmethod + def _ComputeDiskBarrierArgs(cls, disabled_barriers, disable_meta_flush): + """Compute the DRBD command line parameters for disk barriers + + """ + disabled_barriers_set = frozenset(disabled_barriers) + if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT: + raise errors.BlockDeviceError("%s is not a valid option set for DRBD" + " barriers" % disabled_barriers) + + args = [] + + # meta flushes + if disable_meta_flush: + args.append(cls._DISABLE_META_FLUSH_OPTION) + + # disk flushes + if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set: + args.append(cls._DISABLE_FLUSH_OPTION) + + # disk drain + if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set: + args.append(cls._DISABLE_DRAIN_OPTION) + + # disk barriers + if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set: + args.append(cls._DISABLE_DISK_OPTION) + + return args diff --git a/test/py/ganeti.block.drbd_unittest.py b/test/py/ganeti.block.drbd_unittest.py index 87b3d031026ae8409ea2694ee9e53275fe682991..112f5b101586021b1efdfc53abb75670cb0c264c 100755 --- a/test/py/ganeti.block.drbd_unittest.py +++ b/test/py/ganeti.block.drbd_unittest.py @@ -413,8 +413,7 @@ class TestDRBD8Construction(testutils.GanetiTestCase): inst = drbd.DRBD8(self.test_unique_id, [], 123, {}) self.assertEqual(inst._show_info_cls, drbd_info.DRBD84ShowInfo) - # FIXME: add assertion for right class here! - self.assertEqual(inst._cmd_gen, None) + self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD84CmdGenerator)) if __name__ == "__main__":