All functionality specific to a single DRBD8 devide is now in DRBD8Dev, whereas functionality which is valid for the whole DRBD "installation" on a device is collected in DRBD8.
This makes it possible to remove a couple of FIXME's and clarifies the code in some areas. Signed-off-by: Thomas Thrainer <[email protected]> Conflicts: lib/block/drbd.py --- lib/backend.py | 10 +- lib/block/drbd.py | 280 ++++++++++++++++++---------------- lib/bootstrap.py | 2 +- lib/watcher/nodemaint.py | 7 +- test/py/ganeti.block.drbd_unittest.py | 10 +- 5 files changed, 160 insertions(+), 149 deletions(-) diff --git a/lib/backend.py b/lib/backend.py index 147f35c..dc13b02 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -66,7 +66,7 @@ from ganeti import pathutils from ganeti import vcluster from ganeti import ht from ganeti.block.base import BlockDev -from ganeti.block.drbd_info import DRBD8Info +from ganeti.block.drbd import DRBD8 from ganeti import hooksmaster @@ -833,7 +833,7 @@ def VerifyNode(what, cluster_name): if constants.NV_DRBDVERSION in what and vm_capable: try: - drbd_version = DRBD8Info.CreateFromFile().GetVersionString() + drbd_version = DRBD8.GetProcInfo().GetVersionString() except errors.BlockDeviceError, err: logging.warning("Can't get DRBD version", exc_info=True) drbd_version = str(err) @@ -841,7 +841,7 @@ def VerifyNode(what, cluster_name): if constants.NV_DRBDLIST in what and vm_capable: try: - used_minors = drbd.DRBD8Dev.GetUsedDevs() + used_minors = drbd.DRBD8.GetUsedDevs() except errors.BlockDeviceError, err: logging.warning("Can't get used minors list", exc_info=True) used_minors = str(err) @@ -850,7 +850,7 @@ def VerifyNode(what, cluster_name): if constants.NV_DRBDHELPER in what and vm_capable: status = True try: - payload = drbd.DRBD8Dev.GetUsermodeHelper() + payload = drbd.DRBD8.GetUsermodeHelper() except errors.BlockDeviceError, err: logging.error("Can't get DRBD usermode helper: %s", str(err)) status = False @@ -3692,7 +3692,7 @@ def GetDrbdUsermodeHelper(): """ try: - return drbd.DRBD8Dev.GetUsermodeHelper() + return drbd.DRBD8.GetUsermodeHelper() except errors.BlockDeviceError, err: _Fail(str(err)) diff --git a/lib/block/drbd.py b/lib/block/drbd.py index 3912f3d..dc1bba2 100644 --- a/lib/block/drbd.py +++ b/lib/block/drbd.py @@ -42,6 +42,102 @@ from ganeti.block import drbd_cmdgen _DEVICE_READ_SIZE = 128 * 1024 +class DRBD8(object): + """Various methods to deals with the DRBD system as a whole. + + This class provides a set of methods to deal with the DRBD installation on + the node or with uninitialized devices as opposed to a DRBD device. + + """ + _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper" + + _MAX_MINORS = 255 + + @staticmethod + def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE): + """Returns DRBD usermode_helper currently set. + + """ + try: + helper = utils.ReadFile(filename).splitlines()[0] + except EnvironmentError, err: + if err.errno == errno.ENOENT: + base.ThrowError("The file %s cannot be opened, check if the module" + " is loaded (%s)", filename, str(err)) + else: + base.ThrowError("Can't read DRBD helper file %s: %s", + filename, str(err)) + if not helper: + base.ThrowError("Can't read any data from %s", filename) + return helper + + @staticmethod + def GetProcInfo(): + """Reads and parses information from /proc/drbd. + + @return: a L{DRBD8Info} instance + + """ + return DRBD8Info.CreateFromFile() + + @staticmethod + def GetUsedDevs(): + """Compute the list of used DRBD devices. + + """ + drbd_info = DRBD8.GetProcInfo() + return filter(lambda m: not drbd_info.GetMinorStatus(m).is_unconfigured, + drbd_info.GetMinors()) + + @staticmethod + def FindUnusedMinor(): + """Find an unused DRBD device. + + This is specific to 8.x as the minors are allocated dynamically, + so non-existing numbers up to a max minor count are actually free. + + """ + highest = None + drbd_info = DRBD8.GetProcInfo() + for minor in drbd_info.GetMinors(): + status = drbd_info.GetMinorStatus(minor) + if not status.is_in_use: + return minor + highest = max(highest, minor) + + if highest is None: # there are no minors in use at all + return 0 + if highest >= DRBD8._MAX_MINORS: + logging.error("Error: no free drbd minors!") + raise errors.BlockDeviceError("Can't find a free DRBD minor") + + return highest + 1 + + @staticmethod + def GetCmdGenerator(drbd_info): + version = drbd_info.GetVersion() + if version["k_minor"] <= 3: + return drbd_cmdgen.DRBD83CmdGenerator(version) + else: + return drbd_cmdgen.DRBD84CmdGenerator(version) + + @staticmethod + def ShutdownAll(minor): + """Deactivate the device. + + This will, of course, fail if the device is in use. + + """ + drbd_info = DRBD8.GetProcInfo() + cmd_gen = DRBD8.GetCmdGenerator(drbd_info) + + cmd = cmd_gen.GenDownCmd(minor) + result = utils.RunCmd(cmd) + if result.failed: + base.ThrowError("drbd%d: can't shutdown drbd device: %s", + minor, result.output) + + class DRBD8Dev(base.BlockDev): """DRBD v8.x block device. @@ -57,10 +153,6 @@ class DRBD8Dev(base.BlockDev): """ _DRBD_MAJOR = 147 - _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper" - - _MAX_MINORS = 255 - # timeout constants _NET_RECONFIG_TIMEOUT = 60 @@ -81,7 +173,7 @@ class DRBD8Dev(base.BlockDev): super(DRBD8Dev, self).__init__(unique_id, children, size, params) self.major = self._DRBD_MAJOR - drbd_info = DRBD8Info.CreateFromFile() + drbd_info = DRBD8.GetProcInfo() version = drbd_info.GetVersion() if version["k_major"] != 8: base.ThrowError("Mismatch in DRBD kernel version and requested ganeti" @@ -93,7 +185,7 @@ class DRBD8Dev(base.BlockDev): else: self._show_info_cls = DRBD84ShowInfo - self._cmd_gen = self._GetCmdGenerator(drbd_info) + self._cmd_gen = DRBD8.GetCmdGenerator(drbd_info) if (self._lhost is not None and self._lhost == self._rhost and self._lport == self._rport): @@ -101,32 +193,6 @@ class DRBD8Dev(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. - - """ - try: - helper = utils.ReadFile(filename).splitlines()[0] - except EnvironmentError, err: - if err.errno == errno.ENOENT: - base.ThrowError("The file %s cannot be opened, check if the module" - " is loaded (%s)", filename, str(err)) - else: - base.ThrowError("Can't read DRBD helper file %s: %s", - filename, str(err)) - if not helper: - base.ThrowError("Can't read any data from %s", filename) - return helper - @staticmethod def _DevPath(minor): """Return the path to a drbd device for a given minor. @@ -134,15 +200,6 @@ class DRBD8Dev(base.BlockDev): """ return "/dev/drbd%d" % minor - @classmethod - def GetUsedDevs(cls): - """Compute the list of used DRBD devices. - - """ - drbd_info = DRBD8Info.CreateFromFile() - return filter(lambda m: not drbd_info.GetMinorStatus(m).is_unconfigured, - drbd_info.GetMinors()) - def _SetFromMinor(self, minor): """Set our parameters based on the given minor. @@ -187,56 +244,6 @@ class DRBD8Dev(base.BlockDev): base.ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024)) - @classmethod - def _InitMeta(cls, minor, dev_path): - """Initialize a meta device. - - This will not work if the given minor is in use. - - """ - # Zero the metadata first, in order to make sure drbdmeta doesn't - # try to auto-detect existing filesystems or similar (see - # http://code.google.com/p/ganeti/issues/detail?id=182); we only - # care about the first 128MB of data in the device, even though it - # can be bigger - result = utils.RunCmd([constants.DD_CMD, - "if=/dev/zero", "of=%s" % dev_path, - "bs=1048576", "count=128", "oflag=direct"]) - if result.failed: - base.ThrowError("Can't wipe the meta device: %s", result.output) - - 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) - - def _FindUnusedMinor(self): - """Find an unused DRBD device. - - This is specific to 8.x as the minors are allocated dynamically, - so non-existing numbers up to a max minor count are actually free. - - """ - - highest = None - drbd_info = DRBD8Info.CreateFromFile() - for minor in drbd_info.GetMinors(): - status = drbd_info.GetMinorStatus(minor) - if not status.is_in_use: - return minor - highest = max(highest, minor) - - if highest is None: # there are no minors in use at all - return 0 - if highest >= self._MAX_MINORS: - logging.error("Error: no free drbd minors!") - raise errors.BlockDeviceError("Can't find a free DRBD minor") - - return highest + 1 - def _GetShowData(self, minor): """Return the `drbdsetup show` data for a minor. @@ -400,7 +407,7 @@ class DRBD8Dev(base.BlockDev): backend.Open() meta.Open() self._CheckMetaSize(meta.dev_path) - self._InitMeta(self._FindUnusedMinor(), meta.dev_path) + self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path) self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size) self._children = devices @@ -506,7 +513,7 @@ class DRBD8Dev(base.BlockDev): if self.minor is None: base.ThrowError("drbd%d: GetStats() called while not attached", self._aminor) - drbd_info = DRBD8Info.CreateFromFile() + drbd_info = DRBD8.GetProcInfo() if not drbd_info.HasMinorStatus(self.minor): base.ThrowError("drbd%d: can't find myself in /proc", self.minor) return drbd_info.GetMinorStatus(self.minor) @@ -685,7 +692,7 @@ class DRBD8Dev(base.BlockDev): /proc). """ - used_devs = self.GetUsedDevs() + used_devs = DRBD8.GetUsedDevs() if self._aminor in used_devs: minor = self._aminor else: @@ -840,25 +847,6 @@ class DRBD8Dev(base.BlockDev): base.ThrowError("drbd%d: can't shutdown network: %s", minor, result.output) - @classmethod - def _ShutdownAll(cls, minor): - """Deactivate the device. - - This will, of course, fail if the device is in use. - - """ - # 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 properly fixed. - drbd_info = DRBD8Info.CreateFromFile() - cmd_gen = cls._GetCmdGenerator(drbd_info) - - cmd = cmd_gen.GenDownCmd(minor) - result = utils.RunCmd(cmd) - if result.failed: - base.ThrowError("drbd%d: can't shutdown drbd device: %s", - minor, result.output) - def Shutdown(self): """Shutdown the DRBD device. @@ -869,7 +857,7 @@ class DRBD8Dev(base.BlockDev): minor = self.minor self.minor = None self.dev_path = None - self._ShutdownAll(minor) + DRBD8.ShutdownAll(minor) def Remove(self): """Stub remove for DRBD devices. @@ -885,6 +873,50 @@ class DRBD8Dev(base.BlockDev): """ raise errors.ProgrammerError("Can't rename a drbd device") + def Grow(self, amount, dryrun, backingstore): + """Resize the DRBD device and its backing storage. + + """ + if self.minor is None: + base.ThrowError("drbd%d: Grow called while not attached", self._aminor) + if len(self._children) != 2 or None in self._children: + base.ThrowError("drbd%d: cannot grow diskless device", self.minor) + self._children[0].Grow(amount, dryrun, backingstore) + if dryrun or backingstore: + # DRBD does not support dry-run mode and is not backing storage, + # so we'll return here + return + cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount) + result = utils.RunCmd(cmd) + if result.failed: + base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output) + + @classmethod + def _InitMeta(cls, minor, dev_path): + """Initialize a meta device. + + This will not work if the given minor is in use. + + """ + # Zero the metadata first, in order to make sure drbdmeta doesn't + # try to auto-detect existing filesystems or similar (see + # http://code.google.com/p/ganeti/issues/detail?id=182); we only + # care about the first 128MB of data in the device, even though it + # can be bigger + result = utils.RunCmd([constants.DD_CMD, + "if=/dev/zero", "of=%s" % dev_path, + "bs=1048576", "count=128", "oflag=direct"]) + if result.failed: + base.ThrowError("Can't wipe the meta device: %s", result.output) + + drbd_info = DRBD8.GetProcInfo() + cmd_gen = DRBD8.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) + @classmethod def Create(cls, unique_id, children, size, params, excl_stor): """Create a new DRBD8 device. @@ -901,7 +933,7 @@ class DRBD8Dev(base.BlockDev): # check that the minor is unused aminor = unique_id[4] - drbd_info = DRBD8Info.CreateFromFile() + drbd_info = DRBD8.GetProcInfo() if drbd_info.HasMinorStatus(aminor): status = drbd_info.GetMinorStatus(aminor) in_use = status.is_in_use @@ -919,24 +951,6 @@ class DRBD8Dev(base.BlockDev): cls._InitMeta(aminor, meta.dev_path) return cls(unique_id, children, size, params) - def Grow(self, amount, dryrun, backingstore): - """Resize the DRBD device and its backing storage. - - """ - if self.minor is None: - base.ThrowError("drbd%d: Grow called while not attached", self._aminor) - if len(self._children) != 2 or None in self._children: - base.ThrowError("drbd%d: cannot grow diskless device", self.minor) - self._children[0].Grow(amount, dryrun, backingstore) - if dryrun or backingstore: - # DRBD does not support dry-run mode and is not backing storage, - # so we'll return here - return - cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount) - result = utils.RunCmd(cmd) - if result.failed: - base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output) - def _CanReadDevice(path): """Check if we can read from the given device. diff --git a/lib/bootstrap.py b/lib/bootstrap.py index fba3315..d648c77 100644 --- a/lib/bootstrap.py +++ b/lib/bootstrap.py @@ -470,7 +470,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914 if drbd_helper is not None: try: - curr_helper = drbd.DRBD8Dev.GetUsermodeHelper() + curr_helper = drbd.DRBD8.GetUsermodeHelper() except errors.BlockDeviceError, err: raise errors.OpPrereqError("Error while checking drbd helper" " (specify --no-drbd-storage if you are not" diff --git a/lib/watcher/nodemaint.py b/lib/watcher/nodemaint.py index ed4157e..c3b82c0 100644 --- a/lib/watcher/nodemaint.py +++ b/lib/watcher/nodemaint.py @@ -80,7 +80,7 @@ class NodeMaintenance(object): """Get list of used DRBD minors. """ - return drbd.DRBD8Dev.GetUsedDevs() + return drbd.DRBD8.GetUsedDevs() @classmethod def DoMaintenance(cls, role): @@ -121,10 +121,7 @@ class NodeMaintenance(object): logging.info("Following DRBD minors should not be active," " shutting them down: %s", utils.CommaJoin(drbd_running)) for minor in drbd_running: - # pylint: disable=W0212 - # using the private method as is, pending enhancements to the DRBD - # interface - drbd.DRBD8Dev._ShutdownAll(minor) + drbd.DRBD8.ShutdownAll(minor) def Exec(self): """Check node status versus cluster desired state. diff --git a/test/py/ganeti.block.drbd_unittest.py b/test/py/ganeti.block.drbd_unittest.py index ace363f..fcba8ee 100755 --- a/test/py/ganeti.block.drbd_unittest.py +++ b/test/py/ganeti.block.drbd_unittest.py @@ -295,7 +295,7 @@ class TestDRBD8Status(testutils.GanetiTestCase): def testHelper(self): """Test reading usermode_helper in /sys.""" sys_drbd_helper = testutils.TestDataFilename("sys_drbd_usermode_helper.txt") - drbd_helper = drbd.DRBD8Dev.GetUsermodeHelper(filename=sys_drbd_helper) + drbd_helper = drbd.DRBD8.GetUsermodeHelper(filename=sys_drbd_helper) self.failUnlessEqual(drbd_helper, "/bin/true") def testHelperIOErrors(self): @@ -303,7 +303,7 @@ class TestDRBD8Status(testutils.GanetiTestCase): temp_file = self._CreateTempFile() os.unlink(temp_file) self.failUnlessRaises(errors.BlockDeviceError, - drbd.DRBD8Dev.GetUsermodeHelper, filename=temp_file) + drbd.DRBD8.GetUsermodeHelper, filename=temp_file) def testMinorNotFound(self): """Test not-found-minor in /proc""" @@ -401,7 +401,7 @@ class TestDRBD8Construction(testutils.GanetiTestCase): self.test_unique_id = ("hosta.com", 123, "host2.com", 123, 0, "secret") - @testutils.patch_object(drbd_info.DRBD8Info, "CreateFromFile") + @testutils.patch_object(drbd.DRBD8, "GetProcInfo") def testConstructionWith80Data(self, mock_create_from_file): mock_create_from_file.return_value = self.proc80_info @@ -409,7 +409,7 @@ class TestDRBD8Construction(testutils.GanetiTestCase): self.assertEqual(inst._show_info_cls, drbd_info.DRBD83ShowInfo) self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD83CmdGenerator)) - @testutils.patch_object(drbd_info.DRBD8Info, "CreateFromFile") + @testutils.patch_object(drbd.DRBD8, "GetProcInfo") def testConstructionWith83Data(self, mock_create_from_file): mock_create_from_file.return_value = self.proc83_info @@ -417,7 +417,7 @@ class TestDRBD8Construction(testutils.GanetiTestCase): self.assertEqual(inst._show_info_cls, drbd_info.DRBD83ShowInfo) self.assertTrue(isinstance(inst._cmd_gen, drbd_cmdgen.DRBD83CmdGenerator)) - @testutils.patch_object(drbd_info.DRBD8Info, "CreateFromFile") + @testutils.patch_object(drbd.DRBD8, "GetProcInfo") def testConstructionWith84Data(self, mock_create_from_file): mock_create_from_file.return_value = self.proc84_info -- 1.8.2.1
