diff --git a/mon/mon.js b/mon/mon.js index ae8c0c40..b11ee7cf 100644 --- a/mon/mon.js +++ b/mon/mon.js @@ -6,9 +6,10 @@ const http = require('http'); const crypto = require('crypto'); const os = require('os'); const WebSocket = require('ws'); -const { RuleCombinator, parse_level_indexes, parse_pg_dsl } = require('./dsl_pgs.js'); +const { RuleCombinator } = require('./dsl_pgs.js'); const { SimpleCombinator, flatten_tree } = require('./simple_pgs.js'); const { etcd_tree, etcd_allow, etcd_nonempty_keys } = require('./etcd_schema.js'); +const { validate_pool_cfg, get_pg_rules } = require('./pool_config.js'); const LPOptimizer = require('./lp-optimizer.js'); const stableStringify = require('./stable-stringify.js'); const PGUtil = require('./PGUtil.js'); @@ -370,7 +371,7 @@ class Mon { new_clean_pgs.items[pool_id] = (this.state.history.last_clean_pgs.items||{})[pool_id]; const pool_cfg = this.state.config.pools[pool_id]; - if (!this.validate_pool_cfg(pool_id, pool_cfg, false)) + if (!validate_pool_cfg(pool_id, pool_cfg, this.config.placement_levels, false)) { continue next_pool; } @@ -761,98 +762,6 @@ class Mon } } - validate_pool_cfg(pool_id, pool_cfg, warn) - { - pool_cfg.pg_size = Math.floor(pool_cfg.pg_size); - pool_cfg.pg_minsize = Math.floor(pool_cfg.pg_minsize); - pool_cfg.parity_chunks = Math.floor(pool_cfg.parity_chunks) || undefined; - pool_cfg.pg_count = Math.floor(pool_cfg.pg_count); - pool_cfg.max_osd_combinations = Math.floor(pool_cfg.max_osd_combinations) || 10000; - if (!/^[1-9]\d*$/.exec(''+pool_id)) - { - if (warn) - console.log('Pool ID '+pool_id+' is invalid'); - return false; - } - if (pool_cfg.scheme !== 'xor' && pool_cfg.scheme !== 'replicated' && - pool_cfg.scheme !== 'ec' && pool_cfg.scheme !== 'jerasure') - { - if (warn) - console.log('Pool '+pool_id+' has invalid coding scheme (one of "xor", "replicated", "ec" and "jerasure" required)'); - return false; - } - if (!pool_cfg.pg_size || pool_cfg.pg_size < 1 || pool_cfg.pg_size > 256 || - pool_cfg.scheme !== 'replicated' && pool_cfg.pg_size < 3) - { - if (warn) - console.log('Pool '+pool_id+' has invalid pg_size'); - return false; - } - if (!pool_cfg.pg_minsize || pool_cfg.pg_minsize < 1 || pool_cfg.pg_minsize > pool_cfg.pg_size || - pool_cfg.scheme === 'xor' && pool_cfg.pg_minsize < (pool_cfg.pg_size - 1)) - { - if (warn) - console.log('Pool '+pool_id+' has invalid pg_minsize'); - return false; - } - if (pool_cfg.scheme === 'xor' && pool_cfg.parity_chunks != 0 && pool_cfg.parity_chunks != 1) - { - if (warn) - console.log('Pool '+pool_id+' has invalid parity_chunks (must be 1)'); - return false; - } - if ((pool_cfg.scheme === 'ec' || pool_cfg.scheme === 'jerasure') && - (pool_cfg.parity_chunks < 1 || pool_cfg.parity_chunks > pool_cfg.pg_size-2)) - { - if (warn) - console.log('Pool '+pool_id+' has invalid parity_chunks (must be between 1 and pg_size-2)'); - return false; - } - if (!pool_cfg.pg_count || pool_cfg.pg_count < 1) - { - if (warn) - console.log('Pool '+pool_id+' has invalid pg_count'); - return false; - } - if (!pool_cfg.name) - { - if (warn) - console.log('Pool '+pool_id+' has empty name'); - return false; - } - if (pool_cfg.max_osd_combinations < 100) - { - if (warn) - console.log('Pool '+pool_id+' has invalid max_osd_combinations (must be at least 100)'); - return false; - } - if (pool_cfg.root_node && typeof(pool_cfg.root_node) != 'string') - { - if (warn) - console.log('Pool '+pool_id+' has invalid root_node (must be a string)'); - return false; - } - if (pool_cfg.osd_tags && typeof(pool_cfg.osd_tags) != 'string' && - (!(pool_cfg.osd_tags instanceof Array) || pool_cfg.osd_tags.filter(t => typeof t != 'string').length > 0)) - { - if (warn) - console.log('Pool '+pool_id+' has invalid osd_tags (must be a string or array of strings)'); - return false; - } - if (pool_cfg.primary_affinity_tags && typeof(pool_cfg.primary_affinity_tags) != 'string' && - (!(pool_cfg.primary_affinity_tags instanceof Array) || pool_cfg.primary_affinity_tags.filter(t => typeof t != 'string').length > 0)) - { - if (warn) - console.log('Pool '+pool_id+' has invalid primary_affinity_tags (must be a string or array of strings)'); - return false; - } - if (!this.get_pg_rules(pool_id, pool_cfg, true)) - { - return false; - } - return true; - } - filter_osds_by_root_node(pool_tree, root_node) { if (!root_node) @@ -936,78 +845,10 @@ class Mon return aff_osds; } - get_pg_rules(pool_id, pool_cfg, warn) - { - if (pool_cfg.level_placement) - { - const pg_size = (0|pool_cfg.pg_size); - let rules = pool_cfg.level_placement; - if (typeof rules === 'string') - { - rules = rules.split(/\s+/).map(s => s.split(/=/, 2)).reduce((a, c) => { a[c[0]] = c[1]; return a; }, {}); - } - else - { - rules = { ...rules }; - } - // Always add failure_domain to prevent rules from being totally incorrect - const all_diff = []; - for (let i = 1; i <= pg_size; i++) - { - all_diff.push(i); - } - rules[pool_cfg.failure_domain || 'host'] = all_diff; - const levels = this.config.placement_levels||{}; - levels.host = levels.host || 100; - levels.osd = levels.osd || 101; - for (const k in rules) - { - if (!levels[k] || typeof rules[k] !== 'string' && - (!(rules[k] instanceof Array) || - rules[k].filter(s => typeof s !== 'string' && typeof s !== 'number').length > 0)) - { - if (warn) - console.log('Pool '+pool_id+' configuration is invalid: level_placement should be { [level]: string | (string|number)[] }'); - return null; - } - else if (rules[k].length != pg_size) - { - if (warn) - console.log('Pool '+pool_id+' configuration is invalid: values in level_placement should contain exactly pg_size ('+pg_size+') items'); - return null; - } - } - return parse_level_indexes(rules); - } - else if (typeof pool_cfg.raw_placement === 'string') - { - try - { - return parse_pg_dsl(pool_cfg.raw_placement); - } - catch (e) - { - if (warn) - console.log('Pool '+pool_id+' configuration is invalid: invalid raw_placement: '+e.message); - } - } - else - { - let rules = [ [] ]; - let prev = [ 1 ]; - for (let i = 1; i < pool_cfg.pg_size; i++) - { - rules.push([ [ pool_cfg.failure_domain||'host', '!=', prev ] ]); - prev = [ ...prev, i+1 ]; - } - return rules; - } - } - async generate_pool_pgs(pool_id, osd_tree, levels) { const pool_cfg = this.state.config.pools[pool_id]; - if (!this.validate_pool_cfg(pool_id, pool_cfg, false)) + if (!validate_pool_cfg(pool_id, pool_cfg, this.config.placement_levels, false)) { return null; } @@ -1040,7 +881,7 @@ class Mon osd_weights: Object.values(pool_tree).filter(item => item.level === 'osd').reduce((a, c) => { a[c.id] = c.size; return a; }, {}), combinator: !this.config.use_old_pg_combinator || pool_cfg.level_placement || pool_cfg.raw_placement // new algorithm: - ? new RuleCombinator(pool_tree, this.get_pg_rules(pool_id, pool_cfg), pool_cfg.max_osd_combinations) + ? new RuleCombinator(pool_tree, get_pg_rules(pool_id, pool_cfg, this.config.placement_levels), pool_cfg.max_osd_combinations) // old algorithm: : new SimpleCombinator(flatten_tree(pool_tree[''].children, levels, pool_cfg.failure_domain, 'osd'), pool_cfg.pg_size, pool_cfg.max_osd_combinations), pg_count: pool_cfg.pg_count, @@ -1163,7 +1004,7 @@ class Mon for (const pool_id in this.state.config.pools) { const pool_cfg = this.state.config.pools[pool_id]; - if (!this.validate_pool_cfg(pool_id, pool_cfg, false)) + if (!validate_pool_cfg(pool_id, pool_cfg, this.config.placement_levels, false)) { continue; } @@ -1740,7 +1581,7 @@ class Mon { // Adjust pool configuration so PG distribution hash doesn't change on recheck() const pool_cfg = this.state.config.pools[pool_id]; - this.validate_pool_cfg(pool_id, pool_cfg, true); + validate_pool_cfg(pool_id, pool_cfg, this.config.placement_levels, true); } } else if (key_parts[0] === 'osd' && key_parts[1] === 'stats') diff --git a/mon/pool_config.js b/mon/pool_config.js new file mode 100644 index 00000000..28d85767 --- /dev/null +++ b/mon/pool_config.js @@ -0,0 +1,169 @@ +// Copyright (c) Vitaliy Filippov, 2019+ +// License: VNPL-1.1 (see README.md for details) + +const { parse_level_indexes, parse_pg_dsl } = require('./dsl_pgs.js'); + +function validate_pool_cfg(pool_id, pool_cfg, placement_levels, warn) +{ + pool_cfg.pg_size = Math.floor(pool_cfg.pg_size); + pool_cfg.pg_minsize = Math.floor(pool_cfg.pg_minsize); + pool_cfg.parity_chunks = Math.floor(pool_cfg.parity_chunks) || undefined; + pool_cfg.pg_count = Math.floor(pool_cfg.pg_count); + pool_cfg.max_osd_combinations = Math.floor(pool_cfg.max_osd_combinations) || 10000; + if (!/^[1-9]\d*$/.exec(''+pool_id)) + { + if (warn) + console.log('Pool ID '+pool_id+' is invalid'); + return false; + } + if (pool_cfg.scheme !== 'xor' && pool_cfg.scheme !== 'replicated' && + pool_cfg.scheme !== 'ec' && pool_cfg.scheme !== 'jerasure') + { + if (warn) + console.log('Pool '+pool_id+' has invalid coding scheme (one of "xor", "replicated", "ec" and "jerasure" required)'); + return false; + } + if (!pool_cfg.pg_size || pool_cfg.pg_size < 1 || pool_cfg.pg_size > 256 || + pool_cfg.scheme !== 'replicated' && pool_cfg.pg_size < 3) + { + if (warn) + console.log('Pool '+pool_id+' has invalid pg_size'); + return false; + } + if (!pool_cfg.pg_minsize || pool_cfg.pg_minsize < 1 || pool_cfg.pg_minsize > pool_cfg.pg_size || + pool_cfg.scheme === 'xor' && pool_cfg.pg_minsize < (pool_cfg.pg_size - 1)) + { + if (warn) + console.log('Pool '+pool_id+' has invalid pg_minsize'); + return false; + } + if (pool_cfg.scheme === 'xor' && pool_cfg.parity_chunks != 0 && pool_cfg.parity_chunks != 1) + { + if (warn) + console.log('Pool '+pool_id+' has invalid parity_chunks (must be 1)'); + return false; + } + if ((pool_cfg.scheme === 'ec' || pool_cfg.scheme === 'jerasure') && + (pool_cfg.parity_chunks < 1 || pool_cfg.parity_chunks > pool_cfg.pg_size-2)) + { + if (warn) + console.log('Pool '+pool_id+' has invalid parity_chunks (must be between 1 and pg_size-2)'); + return false; + } + if (!pool_cfg.pg_count || pool_cfg.pg_count < 1) + { + if (warn) + console.log('Pool '+pool_id+' has invalid pg_count'); + return false; + } + if (!pool_cfg.name) + { + if (warn) + console.log('Pool '+pool_id+' has empty name'); + return false; + } + if (pool_cfg.max_osd_combinations < 100) + { + if (warn) + console.log('Pool '+pool_id+' has invalid max_osd_combinations (must be at least 100)'); + return false; + } + if (pool_cfg.root_node && typeof(pool_cfg.root_node) != 'string') + { + if (warn) + console.log('Pool '+pool_id+' has invalid root_node (must be a string)'); + return false; + } + if (pool_cfg.osd_tags && typeof(pool_cfg.osd_tags) != 'string' && + (!(pool_cfg.osd_tags instanceof Array) || pool_cfg.osd_tags.filter(t => typeof t != 'string').length > 0)) + { + if (warn) + console.log('Pool '+pool_id+' has invalid osd_tags (must be a string or array of strings)'); + return false; + } + if (pool_cfg.primary_affinity_tags && typeof(pool_cfg.primary_affinity_tags) != 'string' && + (!(pool_cfg.primary_affinity_tags instanceof Array) || pool_cfg.primary_affinity_tags.filter(t => typeof t != 'string').length > 0)) + { + if (warn) + console.log('Pool '+pool_id+' has invalid primary_affinity_tags (must be a string or array of strings)'); + return false; + } + if (!get_pg_rules(pool_id, pool_cfg, placement_levels, true)) + { + return false; + } + return true; +} + +function get_pg_rules(pool_id, pool_cfg, placement_levels, warn) +{ + if (pool_cfg.level_placement) + { + const pg_size = (0|pool_cfg.pg_size); + let rules = pool_cfg.level_placement; + if (typeof rules === 'string') + { + rules = rules.split(/\s+/).map(s => s.split(/=/, 2)).reduce((a, c) => { a[c[0]] = c[1]; return a; }, {}); + } + else + { + rules = { ...rules }; + } + // Always add failure_domain to prevent rules from being totally incorrect + const all_diff = []; + for (let i = 1; i <= pg_size; i++) + { + all_diff.push(i); + } + rules[pool_cfg.failure_domain || 'host'] = all_diff; + placement_levels = placement_levels||{}; + placement_levels.host = placement_levels.host || 100; + placement_levels.osd = placement_levels.osd || 101; + for (const k in rules) + { + if (!placement_levels[k] || typeof rules[k] !== 'string' && + (!(rules[k] instanceof Array) || + rules[k].filter(s => typeof s !== 'string' && typeof s !== 'number').length > 0)) + { + if (warn) + console.log('Pool '+pool_id+' configuration is invalid: level_placement should be { [level]: string | (string|number)[] }'); + return null; + } + else if (rules[k].length != pg_size) + { + if (warn) + console.log('Pool '+pool_id+' configuration is invalid: values in level_placement should contain exactly pg_size ('+pg_size+') items'); + return null; + } + } + return parse_level_indexes(rules); + } + else if (typeof pool_cfg.raw_placement === 'string') + { + try + { + return parse_pg_dsl(pool_cfg.raw_placement); + } + catch (e) + { + if (warn) + console.log('Pool '+pool_id+' configuration is invalid: invalid raw_placement: '+e.message); + } + } + else + { + let rules = [ [] ]; + let prev = [ 1 ]; + for (let i = 1; i < pool_cfg.pg_size; i++) + { + rules.push([ [ pool_cfg.failure_domain||'host', '!=', prev ] ]); + prev = [ ...prev, i+1 ]; + } + return rules; + } +} + +module.exports = { + validate_pool_cfg, + get_pg_rules, +};