#
# Copyright (c) 2018 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
"""
Defaults - manage Albireo/VDO defaults
$Id: //eng/vdo-releases/magnesium/src/python/vdo/vdomgmnt/Defaults.py#17 $
"""
from . import Constants, MgmntLogger, MgmntUtils, SizeString, UserExitStatus
import os
import re
import stat
class ArgumentError(UserExitStatus, Exception):
"""Exception raised to indicate an error with an argument."""
######################################################################
# Overridden methods
######################################################################
def __init__(self, msg, *args, **kwargs):
super(ArgumentError, self).__init__(*args, **kwargs)
self._msg = msg
######################################################################
def __str__(self):
return self._msg
########################################################################
class Defaults(object):
"""Defaults manages default values for arguments."""
NOTSET = -1
ackThreads = 1
ackThreadsMax = 100
ackThreadsMin = 0
activate = Constants.enabled
bioRotationInterval = 64
bioRotationIntervalMax = 1024
bioRotationIntervalMin = 1
bioThreadOverheadMB = 18 # MB
bioThreadReadCacheOverheadMB = 1.12 # per read cache MB overhead in MB
bioThreads = 4
bioThreadsMax = 100
bioThreadsMin = 1
blockMapCacheSize = SizeString("128M")
blockMapCacheSizeMaxPlusOne = SizeString("16T")
blockMapCacheSizeMin = SizeString("128M")
blockMapCacheSizeMinPerLogicalThread = 16 * MgmntUtils.MiB
blockMapPeriod = 16380
blockMapPeriodMax = 16380
blockMapPeriodMin = 1
cfreq = 0
confFile = os.getenv('VDO_CONF_DIR', '/etc') + '/vdoconf.yml'
compression = Constants.enabled
deduplication = Constants.enabled
cpuThreads = 2
cpuThreadsMax = 100
cpuThreadsMin = 1
emulate512 = Constants.disabled
hashZoneThreads = 1
hashZoneThreadsMax = 100
hashZoneThreadsMin = 0
indexMem = 0.25
indexMemIntMax = 1024
indexMemIntMin = 1
log = MgmntLogger.getLogger(MgmntLogger.myname + '.Defaults')
logicalSizeMax = SizeString("4096T")
logicalThreads = 1
logicalThreadsBlockMapCacheSizeThreshold = 9
logicalThreadsMax = 100
logicalThreadsMin = 0
mdRaid5Mode = 'on'
physicalThreadOverheadMB = 10 # MB
physicalThreads = 1
physicalThreadsMax = 16
physicalThreadsMin = 0
readCache = Constants.disabled
readCacheSize = SizeString("0")
readCacheSizeMaxPlusOne = SizeString("16T")
readCacheSizeMin = SizeString("0")
slabSize = SizeString("2G")
slabSizeMax = SizeString("32G")
slabSizeMin = SizeString("128M")
sparseIndex = Constants.disabled
udsParallelFactor = 0
vdoPhysicalBlockSize = 4096
vdoLogLevel = 'info'
vdoLogLevelChoices = ['critical', 'error', 'warning', 'notice', 'info',
'debug']
vdoTargetName = 'vdo'
writePolicy = 'auto'
writePolicyChoices = ['async', 'sync', 'auto']
######################################################################
# Public methods
######################################################################
@staticmethod
def checkAbspath(value):
"""Checks that an option is an absolute pathname.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The pathname as a string.
Raises:
ArgumentError
"""
if os.path.isabs(value):
return value
raise ArgumentError(_("must be an absolute pathname"))
######################################################################
@staticmethod
def checkBlkDev(value):
"""Checks that an option is a valid name for the backing store.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The backing store name as a string.
Raises:
ArgumentError
"""
return Defaults.checkAbspath(value)
######################################################################
@staticmethod
def checkBlockMapPeriod(value):
"""Checks that an option is an acceptable value for the block map period.
The number must be at least 1 and no bigger than 16380.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to an int.
Raises:
ArgumentError
"""
return Defaults._rangeCheck(Defaults.blockMapPeriodMin,
Defaults.blockMapPeriodMax,
value)
######################################################################
@staticmethod
def checkConfFile(value):
"""Checks that an option specifies a possible config file path name.
Currently the only restriction is that the if the path already
exists, it must be a regular file.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value.
Raises:
ArgumentError
"""
if value is not None and not os.path.isfile(value):
# Get a nicer error message if value refers to a directory or
# block device, anything else will be handled later.
return Defaults._checkNotBlockFileOrDirectory(value)
return value
######################################################################
@staticmethod
def checkIndexmem(value):
"""Checks that an option is a legitimate index memory setting.
To handle non-US locales while still supporting the documented
behavior, we allow either a period or a comma as the decimal
separator. This will be normalized to the decimal-point
representation; internally indexMem is always either the string
representation of an integer or one of the exact strings '0.25',
'0.5', or '0.75' regardless of locale.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The memory setting as a string.
Raises:
ArgumentError
"""
value = value.replace(",", ".")
try:
if value == '0.25' or value == '0.5' or value == '0.75':
return value
number = int(value)
if Defaults.indexMemIntMin <= number <= Defaults.indexMemIntMax:
return value
except ValueError:
pass
raise ArgumentError(
_("must be an integer at least {0} and less than or equal to {1}"
" or one of the special values of 0.25, 0.5, or 0.75")
.format(Defaults.indexMemIntMin, Defaults.indexMemIntMax))
######################################################################
@staticmethod
def checkLogFile(value):
"""Checks that an option specifies a possible log file path name.
Currently the only restriction is that the path name may not refer
to an existing block device node or directory in the file system.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value.
Raises:
ArgumentError
"""
return Defaults._checkNotBlockFileOrDirectory(value)
######################################################################
@staticmethod
def checkLogicalSize(value):
"""Checks that an option is an LVM-style size string.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to a SizeString.
Raises:
ArgumentError
"""
ss = Defaults.checkSize(value)
if not (ss <= Defaults.logicalSizeMax):
raise ArgumentError(
_("must be less than or equal to {0}").format(Defaults.logicalSizeMax))
return ss
######################################################################
@staticmethod
def checkPageCachesz(value):
"""Checks that an option is an acceptable value for the page cache size.
Page cache sizes will be rounded up to a multiple of the page size, and
must be at least 128M and less than 16T.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to a SizeString.
Raises:
ArgumentError
"""
ss = Defaults.checkSiSize(value)
if (not (Defaults.blockMapCacheSizeMin
<= ss < Defaults.blockMapCacheSizeMaxPlusOne)):
raise ArgumentError(
_("must be at least {0} and less than {1}")
.format(Defaults.blockMapCacheSizeMin,
Defaults.blockMapCacheSizeMaxPlusOne))
return ss
######################################################################
@staticmethod
def checkReadCachesz(value):
"""Checks that an option is an acceptable value for the read cache size.
Read cache sizes will be rounded to a multiple of 4k, and must be less
than 16T.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to a SizeString.
Raises:
ArgumentError
"""
ss = Defaults.checkSiSize(value)
if (not (Defaults.readCacheSizeMin
<= ss < Defaults.readCacheSizeMaxPlusOne)):
raise ArgumentError(
_("must be at least {0} and less than {1}")
.format(Defaults.readCacheSizeMin,
Defaults.readCacheSizeMaxPlusOne))
return ss
######################################################################
@staticmethod
def checkPhysicalThreadCount(value):
"""Checks that an option is a valid "physical" thread count.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to an integer.
Raises:
ArgumentError
"""
return Defaults._rangeCheck(Defaults.physicalThreadsMin,
Defaults.physicalThreadsMax,
value)
######################################################################
@staticmethod
def checkRotationInterval(value):
"""Checks that an option is a valid bio rotation interval.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to an integer.
Raises:
ArgumentError
"""
return Defaults._rangeCheck(Defaults.bioRotationIntervalMin,
Defaults.bioRotationIntervalMax,
value)
######################################################################
@staticmethod
def checkSiSize(value):
"""Checks that an option is an SI unit size string.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to a SizeString.
Raises:
ArgumentError
"""
if (value[-1].isdigit()
or (value[-1].isalpha()
and (value[-1].lower() in Constants.lvmSiSuffixes))):
try:
ss = SizeString(value)
return ss
except ValueError:
pass
raise ArgumentError(_("must be an SI-style size string"))
######################################################################
@staticmethod
def checkSize(value):
"""Checks that an option is an LVM-style size string.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to a SizeString.
Raises:
ArgumentError
"""
try:
ss = SizeString(value)
return ss
except ValueError:
pass
raise ArgumentError(_("must be an LVM-style size string"))
######################################################################
@staticmethod
def checkSlabSize(value):
"""Checks that an option is a valid slab size.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to a SizeString.
Raises:
ArgumentError
"""
try:
ss = SizeString(value)
size = ss.toBytes()
if ((not MgmntUtils.isPowerOfTwo(size))
or (not (Defaults.slabSizeMin <= ss <= Defaults.slabSizeMax))):
raise ArgumentError(
_("must be a power of two between {0} and {1}")
.format(Defaults.slabSizeMin, Defaults.slabSizeMax))
return ss
except ValueError:
pass
raise ArgumentError(_("must be an LVM-style size string"))
######################################################################
@staticmethod
def checkThreadCount0_100(value):
"""Checks that an option is a valid thread count, for worker thread
types requiring between 0 and 100 threads, inclusive.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to an integer.
Raises:
ArgumentError
"""
return Defaults._rangeCheck(0, 100, value)
######################################################################
@staticmethod
def checkThreadCount1_100(value):
"""Checks that an option is a valid thread count, for worker thread
types requiring between 1 and 100 threads, inclusive.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value converted to an integer.
Raises:
ArgumentError
"""
return Defaults._rangeCheck(1, 100, value)
######################################################################
@staticmethod
def checkVDOName(value):
"""Checks that an option is a valid VDO device name.
The "dmsetup create" command will accept a lot of characters that
could be problematic for udev or for running shell commands
without careful quoting. For now, we permit only alphanumerics and
a few other characters.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The device name as a string.
Raises:
ArgumentError
"""
# See "whitelisted" characters in
# https://sourceware.org/git/?p=lvm2.git;a=blob;f=libdm/libdm-common.c;h=e983b039276671cae991f9b8b81328760aacac4a;hb=HEAD
#
# N.B.: (1) Moved "-" last so we can put in it brackets in a
# regexp. (2) Removed "=" so "-name=foo" ("-n ame=foo") gets an
# error.
allowedChars = "A-Za-z0-9#+.:@_-"
if re.match("^[" + allowedChars + "]*$", value) is None:
raise ArgumentError(
_("VDO device names may only contain characters"
" in '{0}': bad value '{1}'").format(allowedChars, value))
if value.startswith("-"):
raise ArgumentError(
_("VDO device names may not start with '-': bad value '{0}'")
.format(value))
return value
######################################################################
# Overridden methods
######################################################################
def __init__(self):
pass
######################################################################
@staticmethod
def _checkNotBlockFileOrDirectory(value):
"""Checks that an option does not specify an existing block device
or directory.
Arguments:
value (str): Value provided as an argument to the option.
Returns:
The value.
Raises:
ArgumentError
"""
if value is not None and os.path.exists(value):
pathstat = os.stat(value)
if stat.S_ISBLK(pathstat.st_mode):
raise ArgumentError(_("{0} is a block device").format(value))
if stat.S_ISDIR(pathstat.st_mode):
raise ArgumentError(_("{0} is a directory").format(value))
return value
######################################################################
@staticmethod
def _rangeCheck(minValue, maxValue, value):
"""Checks that an option is a valid integer within a desired range.
Arguments:
minValue (int): The minimum acceptable value.
maxValue (int): The maximum acceptable value.
value (str): Value provided as an argument to the option.
Returns:
The value converted to an integer.
Raises:
ArgumentError
"""
try:
number = int(value)
if minValue <= number <= maxValue:
return number
except ValueError:
pass
raise ArgumentError(
_("must be an integer at least {0} and less than or equal to {1}")
.format(minValue, maxValue))