Add command generator for DRBD 8.4
authorThomas Thrainer <thomasth@google.com>
Mon, 29 Apr 2013 11:51:28 +0000 (13:51 +0200)
committerMichele Tartara <mtartara@google.com>
Mon, 29 Apr 2013 16:05:46 +0000 (16:05 +0000)
The DRBD84CmdGenerator class, which generates commands suited for DRBD
8.4, is added. A common baseclass for DRBD83CmdGenerator and
DRBD84CmdGenerator is introduced as well.

Signed-off-by: Thomas Thrainer <thomasth@google.com>
Signed-off-by: Michele Tartara <mtartara@google.com>
Reviewed-by: Michele Tartara <mtartara@google.com>

lib/block/drbd.py
lib/block/drbd_cmdgen.py
test/py/ganeti.block.drbd_unittest.py

index 3df7db7..b69a821 100644 (file)
@@ -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)
index 0877279..35ee875 100644 (file)
@@ -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
index 87b3d03..112f5b1 100755 (executable)
@@ -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__":