blob: b65cf0685a48caa16127ac5ea16c1ea8919bf80f [file] [log] [blame]
#
#
# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""DRBD command generating classes"""
import logging
import shlex
from ganeti import constants
from ganeti import errors
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
_DISABLE_DISK_OPTION = "--no-disk-barrier" # -a
_DISABLE_DRAIN_OPTION = "--no-disk-drain" # -D
_DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
_DISABLE_META_FLUSH_OPTION = "--no-md-flushes" # -m
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",
"-e", "detach",
"--create-device"]
if size_mb:
args.extend(["-d", "%sm" % size_mb])
vmaj = self._version["k_major"]
vmin = self._version["k_minor"]
vrel = self._version["k_point"]
barrier_args = \
self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
params[constants.LDP_BARRIERS],
params[constants.LDP_NO_META_FLUSH])
args.extend(barrier_args)
if params[constants.LDP_DISK_CUSTOM]:
args.extend(shlex.split(params[constants.LDP_DISK_CUSTOM]))
return [args]
def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
dual_pri, hmac, secret, params):
args = ["drbdsetup", self._DevPath(minor), "net",
"%s:%s:%s" % (family, lhost, lport),
"%s:%s:%s" % (family, rhost, rport), protocol,
"-A", "discard-zero-changes",
"-B", "consensus",
"--create-device",
]
if dual_pri:
args.append("-m")
if hmac and secret:
args.extend(["-a", hmac, "-x", 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", self._DevPath(minor), "syncer"]
if params[constants.LDP_DYNAMIC_RESYNC]:
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
if vmin != 3 or vrel < 9:
msg = ("The current DRBD version (8.%d.%d) does not support the "
"dynamic resync speed controller" % (vmin, vrel))
logging.error(msg)
return [msg]
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(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
args.append("--create-device")
return args
def GenPauseSyncCmd(self, minor):
return ["drbdsetup", self._DevPath(minor), "pause-sync"]
def GenResumeSyncCmd(self, minor):
return ["drbdsetup", self._DevPath(minor), "resume-sync"]
def GenPrimaryCmd(self, minor, force):
cmd = ["drbdsetup", self._DevPath(minor), "primary"]
if force:
cmd.append("-o")
return cmd
def GenSecondaryCmd(self, minor):
return ["drbdsetup", self._DevPath(minor), "secondary"]
def GenDetachCmd(self, minor):
return ["drbdsetup", self._DevPath(minor), "detach"]
def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport):
return ["drbdsetup", self._DevPath(minor), "disconnect"]
def GenDownCmd(self, minor):
return ["drbdsetup", self._DevPath(minor), "down"]
def GenResizeCmd(self, minor, size_mb):
return ["drbdsetup", self._DevPath(minor), "resize", "-s", "%dm" % size_mb]
@classmethod
def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
disable_meta_flush):
"""Compute the DRBD command line parameters for disk barriers
Returns a list of the disk barrier parameters as requested via the
disabled_barriers and disable_meta_flush arguments, and according to the
supported ones in the DRBD version vmaj.vmin.vrel
If the desired option is unsupported, raises errors.BlockDeviceError.
"""
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 = []
# The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
# does not exist)
if not vmaj == 8 and vmin in (0, 2, 3):
raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
(vmaj, vmin, vrel))
def _AppendOrRaise(option, min_version):
"""Helper for DRBD options"""
if min_version is not None and vrel >= min_version:
args.append(option)
else:
raise errors.BlockDeviceError("Could not use the option %s as the"
" DRBD version %d.%d.%d does not support"
" it." % (option, vmaj, vmin, vrel))
# the minimum version for each feature is encoded via pairs of (minor
# version -> x) where x is version in which support for the option was
# introduced.
meta_flush_supported = disk_flush_supported = {
0: 12,
2: 7,
3: 0,
}
disk_drain_supported = {
2: 7,
3: 0,
}
disk_barriers_supported = {
3: 0,
}
# meta flushes
if disable_meta_flush:
_AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
meta_flush_supported.get(vmin, None))
# disk flushes
if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
_AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
disk_flush_supported.get(vmin, None))
# disk drain
if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
_AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
disk_drain_supported.get(vmin, None))
# disk barriers
if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
_AppendOrRaise(cls._DISABLE_DISK_OPTION,
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, "flex-external", "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, "flex-external", "apply-al"])
attach_cmd = ["drbdsetup", "attach", minor, data_dev, meta_dev, "flexible",
"--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