from threading import Event
from mgr_module import CRUSHMap
-# available modes: 'none', 'crush', 'crush-compat', 'upmap', 'osd_weight'
-default_mode = 'none'
-default_sleep_interval = 60 # seconds
-
TIME_FORMAT = '%Y-%m-%d_%H:%M:%S'
class MappingState:
class Module(MgrModule):
MODULE_OPTIONS = [
- {'name': 'active'},
- {'name': 'begin_time'},
- {'name': 'crush_compat_max_iterations'},
- {'name': 'crush_compat_metrics'},
- {'name': 'crush_compat_step'},
- {'name': 'end_time'},
- {'name': 'min_score'},
- {'name': 'mode'},
- {'name': 'sleep_interval'},
- {'name': 'upmap_max_iterations'},
- {'name': 'upmap_max_deviation'},
+ {
+ 'name': 'active',
+ 'type': 'bool',
+ 'default': False,
+ 'desc': 'automatically balance PGs across cluster',
+ 'runtime': True,
+ },
+ {
+ 'name': 'begin_time',
+ 'type': 'str',
+ 'default': '0000',
+ 'desc': 'beginning time of day to automatically balance',
+ 'long_desc': 'This is a time of day in the format HHMM.',
+ 'runtime': True,
+ },
+ {
+ 'name': 'end_time',
+ 'type': 'str',
+ 'default': '2400',
+ 'desc': 'ending time of day to automatically balance',
+ 'long_desc': 'This is a time of day in the format HHMM.',
+ 'runtime': True,
+ },
+ {
+ 'name': 'crush_compat_max_iterations',
+ 'type': 'uint',
+ 'default': 25,
+ 'min': '1',
+ 'max': '250',
+ 'desc': 'maximum number of iterations to attempt optimization',
+ 'runtime': True,
+ },
+ {
+ 'name': 'crush_compat_metrics',
+ 'type': 'str',
+ 'default': 'pgs,objects,bytes',
+ 'desc': 'metrics with which to calculate OSD utilization',
+ 'long_desc': 'Value is a list of one or more of "pgs", "objects", or "bytes", and indicates which metrics to use to balance utilization.',
+ 'runtime': True,
+ },
+ {
+ 'name': 'crush_compat_step',
+ 'type': 'float',
+ 'default': .5,
+ 'min': '.001',
+ 'max': '.999',
+ 'desc': 'aggressiveness of optimization',
+ 'long_desc': '.99 is very aggressive, .01 is less aggressive',
+ 'runtime': True,
+ },
+ {
+ 'name': 'min_score',
+ 'type': 'float',
+ 'default': 0,
+ 'desc': 'minimum score, below which no optimization is attempted',
+ 'runtime': True,
+ },
+ {
+ 'name': 'mode',
+ 'desc': 'Balancer mode',
+ 'default': 'none',
+ 'enum_allowed': ['none', 'crush-compat', 'upmap'],
+ 'runtime': True,
+ },
+ {
+ 'name': 'sleep_interval',
+ 'type': 'secs',
+ 'default': 60,
+ 'desc': 'how frequently to wake up and attempt optimization',
+ 'runtime': True,
+ },
+ {
+ 'name': 'upmap_max_iterations',
+ 'type': 'uint',
+ 'default': 10,
+ 'desc': 'maximum upmap optimization iterations',
+ 'runtime': True,
+ },
+ {
+ 'name': 'upmap_max_deviation',
+ 'type': 'float',
+ 'default': .01,
+ 'min': 0,
+ 'max': 1,
+ 'desc': 'deviation below which no optimization is attempted',
+ 'long_desc': 'If the ratio between the fullest and least-full OSD is below this value then we stop trying to optimize placement.',
+ 'runtime': True,
+ },
]
COMMANDS = [
s = {
'plans': list(self.plans.keys()),
'active': self.active,
- 'mode': self.get_module_option('mode', default_mode),
+ 'mode': self.get_module_option('mode'),
}
return (0, json.dumps(s, indent=4), '')
elif command['prefix'] == 'balancer mode':
return (0, '', '')
elif command['prefix'] == 'balancer on':
if not self.active:
- self.set_module_option('active', '1')
+ self.set_module_option('active', 'true')
self.active = True
self.event.set()
return (0, '', '')
elif command['prefix'] == 'balancer off':
if self.active:
- self.set_module_option('active', '')
+ self.set_module_option('active', 'false')
self.active = False
self.event.set()
return (0, '', '')
def serve(self):
self.log.info('Starting')
while self.run:
- self.active = self.get_module_option('active', '') is not ''
- begin_time = self.get_module_option('begin_time') or '0000'
- end_time = self.get_module_option('end_time') or '2400'
+ self.active = self.get_module_option('active')
+ begin_time = self.get_module_option('begin_time')
+ end_time = self.get_module_option('end_time')
timeofday = time.strftime('%H%M', time.localtime())
self.log.debug('Waking up [%s, scheduled for %s-%s, now %s]',
"active" if self.active else "inactive",
begin_time, end_time, timeofday)
- sleep_interval = float(self.get_module_option('sleep_interval',
- default_sleep_interval))
+ sleep_interval = self.get_module_option('sleep_interval')
if self.active and self.time_in_interval(timeofday, begin_time, end_time):
self.log.debug('Running')
name = 'auto_%s' % time.strftime(TIME_FORMAT, time.gmtime())
self.log.debug('score_by_root %s' % pe.score_by_root)
# get the list of score metrics, comma separated
- metrics = self.get_module_option('crush_compat_metrics', 'pgs,objects,bytes').split(',')
+ metrics = self.get_module_option('crush_compat_metrics').split(',')
# total score is just average of normalized stddevs
pe.score = 0.0
def optimize(self, plan):
self.log.info('Optimize plan %s' % plan.name)
- plan.mode = self.get_module_option('mode', default_mode)
+ plan.mode = self.get_module_option('mode')
max_misplaced = float(self.get_ceph_option('target_max_misplaced_ratio'))
self.log.info('Mode %s, max misplaced %f' %
(plan.mode, max_misplaced))
def do_upmap(self, plan):
self.log.info('do_upmap')
- max_iterations = int(self.get_module_option('upmap_max_iterations', 10))
- max_deviation = float(self.get_module_option('upmap_max_deviation', .01))
+ max_iterations = self.get_module_option('upmap_max_iterations')
+ max_deviation = self.get_module_option('upmap_max_deviation')
ms = plan.initial
if len(plan.pools):
def do_crush_compat(self, plan):
self.log.info('do_crush_compat')
- max_iterations = int(self.get_module_option('crush_compat_max_iterations', 25))
+ max_iterations = self.get_module_option('crush_compat_max_iterations')
if max_iterations < 1:
return -errno.EINVAL, '"crush_compat_max_iterations" must be >= 1'
- step = float(self.get_module_option('crush_compat_step', .5))
+ step = self.get_module_option('crush_compat_step')
if step <= 0 or step >= 1.0:
return -errno.EINVAL, '"crush_compat_step" must be in (0, 1)'
max_misplaced = float(self.get_ceph_option('target_max_misplaced_ratio'))
osdmap = ms.osdmap
crush = osdmap.get_crush()
pe = self.calc_eval(ms, plan.pools)
- min_score_to_optimize = float(self.get_module_option('min_score', 0))
+ min_score_to_optimize = self.get_module_option('min_score')
if pe.score <= min_score_to_optimize:
if pe.score == 0:
detail = 'Distribution is already perfect'
return -errno.EOPNOTSUPP, detail
# rebalance by pgs, objects, or bytes
- metrics = self.get_module_option('crush_compat_metrics', 'pgs,objects,bytes').split(',')
+ metrics = self.get_module_option('crush_compat_metrics').split(',')
key = metrics[0] # balancing using the first score metric
if key not in ['pgs', 'bytes', 'objects']:
self.log.warn("Invalid crush_compat balancing key %s. Using 'pgs'." % key)