Compare commits
3 Commits
7dbbab7dc0
...
d55ffc4ab3
Author | SHA1 | Date |
---|---|---|
Vitaliy Filippov | d55ffc4ab3 | |
Vitaliy Filippov | b9017d55b0 | |
Vitaliy Filippov | e8989a6ae5 |
|
@ -0,0 +1,438 @@
|
||||||
|
const NO_OSD = 'Z';
|
||||||
|
|
||||||
|
class RuleCombinator
|
||||||
|
{
|
||||||
|
constructor(osd_tree, rules, max_combinations, ordered)
|
||||||
|
{
|
||||||
|
this.osd_tree = index_tree(Object.values(osd_tree).filter(o => o.id));
|
||||||
|
this.rules = rules;
|
||||||
|
this.max_combinations = max_combinations;
|
||||||
|
this.ordered = ordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
random_combinations()
|
||||||
|
{
|
||||||
|
return random_custom_combinations(this.osd_tree, this.rules, this.max_combinations, this.ordered);
|
||||||
|
}
|
||||||
|
|
||||||
|
check_combinations(pgs)
|
||||||
|
{
|
||||||
|
return check_custom_combinations(this.osd_tree, this.rules, pgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert alternative "level-index" format to rules
|
||||||
|
// level_index = { [level: string]: string | string[] }
|
||||||
|
// level_sequence = optional, levels from upper to lower, i.e. [ 'dc', 'host' ]
|
||||||
|
// Example: level_index = { dc: "112233", host: "ABCDEF" }
|
||||||
|
function parse_level_indexes(level_index, level_sequence)
|
||||||
|
{
|
||||||
|
const rules = [];
|
||||||
|
const lvl_first = {};
|
||||||
|
for (const level in level_index)
|
||||||
|
{
|
||||||
|
const idx = level_index[level];
|
||||||
|
while (rules.length < idx.length)
|
||||||
|
{
|
||||||
|
rules.push([]);
|
||||||
|
}
|
||||||
|
const seen = {};
|
||||||
|
for (let i = 0; i < idx.length; i++)
|
||||||
|
{
|
||||||
|
if (!seen[idx[i]])
|
||||||
|
{
|
||||||
|
const other = Object.values(seen);
|
||||||
|
if (other.length)
|
||||||
|
{
|
||||||
|
rules[i].push([ level, '!=', other ]);
|
||||||
|
}
|
||||||
|
seen[idx[i]] = i+1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rules[i].push([ level, '=', seen[idx[i]] ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lvl_first[level] = seen;
|
||||||
|
}
|
||||||
|
if (level_sequence)
|
||||||
|
{
|
||||||
|
// Prune useless rules for the sake of prettiness
|
||||||
|
// For simplicity, call "upper" level DC and "lower" level host
|
||||||
|
const level_prio = Object.keys(level_sequence).reduce((a, c) => { a[level_sequence[c]] = c; return a; }, {});
|
||||||
|
for (let upper_i = 0; upper_i < level_sequence.length-1; upper_i++)
|
||||||
|
{
|
||||||
|
const upper_level = level_sequence[upper_i];
|
||||||
|
for (let i = 0; i < rules.length; i++)
|
||||||
|
{
|
||||||
|
const noteq = {};
|
||||||
|
for (let k = 0; k < level_index[upper_level].length; k++)
|
||||||
|
{
|
||||||
|
// If upper_level[x] is different from upper_level[y]
|
||||||
|
// then lower_level[x] is also different from lower_level[y]
|
||||||
|
if (level_index[upper_level][k] != level_index[upper_level][i])
|
||||||
|
{
|
||||||
|
noteq[k+1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let j = 0; j < rules[i].length; j++)
|
||||||
|
{
|
||||||
|
if (level_prio[rules[i][j][0]] != null && level_prio[rules[i][j][0]] > upper_i && rules[i][j][1] == '!=')
|
||||||
|
{
|
||||||
|
rules[i][j][2] = rules[i][j][2].filter(other_host => !noteq[other_host]);
|
||||||
|
if (!rules[i][j][2].length)
|
||||||
|
{
|
||||||
|
rules[i].splice(j--, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse rules in DSL format
|
||||||
|
// dsl := item | item ("\n" | ",") items
|
||||||
|
// item := "any" | rules
|
||||||
|
// rules := rule | rule rules
|
||||||
|
// rule := level operator arg
|
||||||
|
// level := /\w+/
|
||||||
|
// operator := "!=" | "=" | ">" | "?="
|
||||||
|
// arg := value | "(" values ")"
|
||||||
|
// values := value | value "," values
|
||||||
|
// value := item_ref | constant_id
|
||||||
|
// item_ref := /\d+/
|
||||||
|
// constant_id := /"([^"]+)"/
|
||||||
|
//
|
||||||
|
// Output: [ level, operator, value ][][]
|
||||||
|
function parse_pg_dsl(text)
|
||||||
|
{
|
||||||
|
const tokens = [ ...text.matchAll(/\w+|!=|\?=|[>=\(\),\n]|"([^\"]+)"/g) ].map(t => [ t[0], t.index ]);
|
||||||
|
let positions = [ [] ];
|
||||||
|
let rules = positions[0];
|
||||||
|
for (let i = 0; i < tokens.length; )
|
||||||
|
{
|
||||||
|
if (tokens[i][0] === '\n' || tokens[i][0] === ',')
|
||||||
|
{
|
||||||
|
rules = [];
|
||||||
|
positions.push(rules);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (!rules.length && tokens[i][0] === 'any' && (i == tokens.length-1 || tokens[i+1][0] === ',' || tokens[i+1][0] === '\n'))
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!/^\w/.exec(tokens[i][0]))
|
||||||
|
{
|
||||||
|
throw new Error('Unexpected '+tokens[i][0]+' at '+tokens[i][1]+' (level name expected)');
|
||||||
|
}
|
||||||
|
if (i > tokens.length-3)
|
||||||
|
{
|
||||||
|
throw new Error('Unexpected EOF (operator and value expected)');
|
||||||
|
}
|
||||||
|
if (/^\w/.exec(tokens[i+1][0]) || tokens[i+1][0] === ',' || tokens[i+1][0] === '\n')
|
||||||
|
{
|
||||||
|
throw new Error('Unexpected '+tokens[i+1][0]+' at '+tokens[i+1][1]+' (operator expected)');
|
||||||
|
}
|
||||||
|
if (!/^[\w"(]/.exec(tokens[i+2][0])) // "
|
||||||
|
{
|
||||||
|
throw new Error('Unexpected '+tokens[i+2][0]+' at '+tokens[i+2][1]+' (id, round brace, number or node ID expected)');
|
||||||
|
}
|
||||||
|
let rule = [ tokens[i][0], tokens[i+1][0], tokens[i+2][0] ];
|
||||||
|
i += 3;
|
||||||
|
if (rule[2][0] == '"')
|
||||||
|
{
|
||||||
|
rule[2] = { id: rule[2].substr(1, rule[2].length-2) };
|
||||||
|
}
|
||||||
|
else if (rule[2] === '(')
|
||||||
|
{
|
||||||
|
rule[2] = [];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (i > tokens.length-1)
|
||||||
|
{
|
||||||
|
throw new Error('Unexpected EOF (expected list and a closing round brace)');
|
||||||
|
}
|
||||||
|
if (tokens[i][0] === ',')
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (tokens[i][0] === ')')
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (tokens[i][0][0] === '"')
|
||||||
|
{
|
||||||
|
rule[2].push({ id: tokens[i][0].substr(1, tokens[i][0].length-2) });
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (/^\d+$/.exec(tokens[i][0]))
|
||||||
|
{
|
||||||
|
const n = 0|tokens[i][0];
|
||||||
|
if (!n)
|
||||||
|
{
|
||||||
|
throw new Error('Level reference cannot be 0 (refs count from 1) at '+tokens[i][1]);
|
||||||
|
}
|
||||||
|
else if (n > positions.length)
|
||||||
|
{
|
||||||
|
throw new Error('Forward references are forbidden at '+tokens[i][1]);
|
||||||
|
}
|
||||||
|
rule[2].push(n);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (!/^\w/.exec(tokens[i][0]))
|
||||||
|
{
|
||||||
|
throw new Error('Unexpected '+tokens[i][0]+' at '+tokens[i][1]+' (number or node ID expected)');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rule[2].push({ id: tokens[i][0] });
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!/^\d+$/.exec(rule[2]))
|
||||||
|
{
|
||||||
|
rule[2] = { id: rule[2] };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rule[2] = 0|rule[2];
|
||||||
|
if (!rule[2])
|
||||||
|
{
|
||||||
|
throw new Error('Level reference cannot be 0 (refs count from 1) at '+tokens[i-1][1]);
|
||||||
|
}
|
||||||
|
else if (rule[2] > positions.length)
|
||||||
|
{
|
||||||
|
throw new Error('Forward references are forbidden at '+tokens[i-1][1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rules.push(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// osd_tree = index_tree() output
|
||||||
|
// levels = { string: number }
|
||||||
|
// rules = [ level, operator, value ][][]
|
||||||
|
// level = string
|
||||||
|
// operator = '=' | '!=' | '>' | '?='
|
||||||
|
// value = number|number[] | { id: string|string[] }
|
||||||
|
// examples:
|
||||||
|
// 1) simple 3 replicas with failure_domain=host:
|
||||||
|
// [ [], [ [ 'host', '!=', 1 ] ], [ [ 'host', '!=', [ 1, 2 ] ] ] ]
|
||||||
|
// in DSL form: any, host!=1, host!=(1,2)
|
||||||
|
// 2) EC 4+2 in 3 DC:
|
||||||
|
// [ [], [ [ 'dc', '=', 1 ], [ 'host', '!=', 1 ] ],
|
||||||
|
// [ 'dc', '!=', 1 ], [ [ 'dc', '=', 3 ], [ 'host', '!=', 3 ] ],
|
||||||
|
// [ 'dc', '!=', [ 1, 3 ] ], [ [ 'dc', '=', 5 ], [ 'host', '!=', 5 ] ] ]
|
||||||
|
// in DSL form: any, dc=1 host!=1, dc!=1, dc=3 host!=3, dc!=(1,3), dc=5 host!=5
|
||||||
|
// 3) 1 replica in fixed DC + 2 in random DCs:
|
||||||
|
// [ [ [ 'dc', '=', { id: 'meow' } ] ], [ [ 'dc', '!=', 1 ] ], [ [ 'dc', '!=', [ 1, 2 ] ] ] ]
|
||||||
|
// in DSL form: dc=meow, dc!=1, dc!=(1,2)
|
||||||
|
// 4) 2 replicas in each DC (almost the same as (2)):
|
||||||
|
// DSL: any, dc=1 host!=1, dc!=1, dc=3 host!=3
|
||||||
|
// Alternative simpler way to specify rules would be: [ DC: 112233 HOST: 123456 ]
|
||||||
|
function random_custom_combinations(osd_tree, rules, count, ordered)
|
||||||
|
{
|
||||||
|
const r = {};
|
||||||
|
const first = filter_tree_by_rules(osd_tree, rules[0], []);
|
||||||
|
let max_size = 0;
|
||||||
|
// All combinations for the first item (usually "any") to try to include each OSD at least once
|
||||||
|
for (const f of first)
|
||||||
|
{
|
||||||
|
const selected = [ f ];
|
||||||
|
for (let i = 1; i < rules.length; i++)
|
||||||
|
{
|
||||||
|
const filtered = filter_tree_by_rules(osd_tree, rules[i], selected);
|
||||||
|
selected.push(select_murmur3(filtered, 'p:'+f.id));
|
||||||
|
}
|
||||||
|
const size = selected.filter(s => s.id !== null).length;
|
||||||
|
max_size = max_size < size ? size : max_size;
|
||||||
|
const pg = selected.map(s => s.id === null ? NO_OSD : s.id);
|
||||||
|
if (!ordered)
|
||||||
|
pg.sort();
|
||||||
|
r['pg_'+pg.join('_')] = pg;
|
||||||
|
}
|
||||||
|
// Pseudo-random selection
|
||||||
|
for (let n = 0; n < count; n++)
|
||||||
|
{
|
||||||
|
const selected = [];
|
||||||
|
for (const item_rules of rules)
|
||||||
|
{
|
||||||
|
const filtered = selected.length ? filter_tree_by_rules(osd_tree, item_rules, selected) : first;
|
||||||
|
selected.push(select_murmur3(filtered, n));
|
||||||
|
}
|
||||||
|
const size = selected.filter(s => s.id !== null).length;
|
||||||
|
max_size = max_size < size ? size : max_size;
|
||||||
|
const pg = selected.map(s => s.id === null ? NO_OSD : s.id);
|
||||||
|
if (!ordered)
|
||||||
|
pg.sort();
|
||||||
|
r['pg_'+pg.join('_')] = pg;
|
||||||
|
}
|
||||||
|
// Exclude PGs with less successful selections than maximum
|
||||||
|
for (const k in r)
|
||||||
|
{
|
||||||
|
if (r[k].filter(s => s !== NO_OSD).length < max_size)
|
||||||
|
{
|
||||||
|
delete r[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
function select_murmur3(filtered, prefix)
|
||||||
|
{
|
||||||
|
if (!filtered.length)
|
||||||
|
{
|
||||||
|
return { levels: {}, id: null };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let i = 0, maxh = -1;
|
||||||
|
for (let j = 0; j < filtered.length; j++)
|
||||||
|
{
|
||||||
|
const h = murmur3(prefix+':'+filtered[j].id);
|
||||||
|
if (h > maxh)
|
||||||
|
{
|
||||||
|
i = j;
|
||||||
|
maxh = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function murmur3(s)
|
||||||
|
{
|
||||||
|
let hash = 0x12345678;
|
||||||
|
for (let i = 0; i < s.length; i++)
|
||||||
|
{
|
||||||
|
hash ^= s.charCodeAt(i);
|
||||||
|
hash = (hash*0x5bd1e995) & 0xFFFFFFFF;
|
||||||
|
hash ^= (hash >> 15);
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filter_tree_by_rules(osd_tree, rules, selected)
|
||||||
|
{
|
||||||
|
let cur = osd_tree[''].children;
|
||||||
|
for (const rule of rules)
|
||||||
|
{
|
||||||
|
const val = (rule[2] instanceof Array ? rule[2] : [ rule[2] ])
|
||||||
|
.map(v => v instanceof Object ? v.id : selected[v-1].levels[rule[0]]);
|
||||||
|
let preferred = [], other = [];
|
||||||
|
for (let i = 0; i < cur.length; i++)
|
||||||
|
{
|
||||||
|
const item = cur[i];
|
||||||
|
const level_id = item.levels[rule[0]];
|
||||||
|
if (level_id)
|
||||||
|
{
|
||||||
|
if (rule[1] == '>' && val.filter(v => level_id <= v).length == 0 ||
|
||||||
|
(rule[1] == '=' || rule[1] == '?=') && val.filter(v => level_id != v).length == 0 ||
|
||||||
|
rule[1] == '!=' && val.filter(v => level_id == v).length == 0)
|
||||||
|
{
|
||||||
|
// Include
|
||||||
|
preferred.push(item);
|
||||||
|
}
|
||||||
|
else if (rule[1] == '?=' && val.filter(v => level_id != v).length > 0)
|
||||||
|
{
|
||||||
|
// Non-preferred
|
||||||
|
other.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.children)
|
||||||
|
{
|
||||||
|
// Descend
|
||||||
|
cur.splice(i+1, 0, ...item.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur = preferred.length ? preferred : other;
|
||||||
|
}
|
||||||
|
// Get leaf items
|
||||||
|
for (let i = 0; i < cur.length; i++)
|
||||||
|
{
|
||||||
|
if (cur[i].children)
|
||||||
|
{
|
||||||
|
// Descend
|
||||||
|
cur.splice(i, 1, ...cur[i].children);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from
|
||||||
|
// node_list = { id: string|number, level: string, size?: number, parent?: string|number }[]
|
||||||
|
// to
|
||||||
|
// node_tree = { [node_id]: { id, level, size?, parent?, children?: child_node_id[], levels: { [level]: id, ... } } }
|
||||||
|
function index_tree(node_list)
|
||||||
|
{
|
||||||
|
const tree = { '': { children: [], levels: {} } };
|
||||||
|
for (const node of node_list)
|
||||||
|
{
|
||||||
|
tree[node.id] = { ...node, levels: {} };
|
||||||
|
delete tree[node.id].children;
|
||||||
|
}
|
||||||
|
for (const node of node_list)
|
||||||
|
{
|
||||||
|
const parent_id = node.parent && tree[node.parent] ? node.parent : '';
|
||||||
|
tree[parent_id].children = tree[parent_id].children || [];
|
||||||
|
tree[parent_id].children.push(tree[node.id]);
|
||||||
|
}
|
||||||
|
const cur = tree[''].children;
|
||||||
|
for (let i = 0; i < cur.length; i++)
|
||||||
|
{
|
||||||
|
cur[i].levels[cur[i].level] = cur[i].id;
|
||||||
|
if (cur[i].children)
|
||||||
|
{
|
||||||
|
for (const child of cur[i].children)
|
||||||
|
{
|
||||||
|
child.levels = { ...cur[i].levels, ...child.levels };
|
||||||
|
}
|
||||||
|
cur.splice(i, 1, ...cur[i].children);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
// selection = id[]
|
||||||
|
// osd_tree = index_tree output
|
||||||
|
// rules = parse_pg_dsl output
|
||||||
|
function check_custom_combinations(osd_tree, rules, pgs)
|
||||||
|
{
|
||||||
|
const res = [];
|
||||||
|
skip_pg: for (const pg of pgs)
|
||||||
|
{
|
||||||
|
let selected = pg.map(id => osd_tree[id] || null);
|
||||||
|
for (let i = 0; i < rules.length; i++)
|
||||||
|
{
|
||||||
|
const filtered = filter_tree_by_rules(osd_tree, rules[i], selected);
|
||||||
|
if (selected[i] === null && filtered.length ||
|
||||||
|
!filtered.filter(ok => selected[i].id === ok.id).length)
|
||||||
|
{
|
||||||
|
continue skip_pg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.push(pg);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
RuleCombinator,
|
||||||
|
NO_OSD,
|
||||||
|
|
||||||
|
index_tree,
|
||||||
|
parse_level_indexes,
|
||||||
|
parse_pg_dsl,
|
||||||
|
random_custom_combinations,
|
||||||
|
check_custom_combinations,
|
||||||
|
};
|
62
mon/mon.js
62
mon/mon.js
|
@ -6,6 +6,7 @@ const http = require('http');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
|
const { RuleCombinator, parse_level_indexes, parse_pg_dsl } = require('./dsl_pgs.js');
|
||||||
const { SimpleCombinator } = require('./simple_pgs.js');
|
const { SimpleCombinator } = require('./simple_pgs.js');
|
||||||
const LPOptimizer = require('./lp-optimizer.js');
|
const LPOptimizer = require('./lp-optimizer.js');
|
||||||
const stableStringify = require('./stable-stringify.js');
|
const stableStringify = require('./stable-stringify.js');
|
||||||
|
@ -64,6 +65,7 @@ const etcd_tree = {
|
||||||
mon_stats_timeout: 1000, // ms. min: 100
|
mon_stats_timeout: 1000, // ms. min: 100
|
||||||
osd_out_time: 600, // seconds. min: 0
|
osd_out_time: 600, // seconds. min: 0
|
||||||
placement_levels: { datacenter: 1, rack: 2, host: 3, osd: 4, ... },
|
placement_levels: { datacenter: 1, rack: 2, host: 3, osd: 4, ... },
|
||||||
|
force_new_pg_combinator: false,
|
||||||
// client and osd
|
// client and osd
|
||||||
tcp_header_buffer_size: 65536,
|
tcp_header_buffer_size: 65536,
|
||||||
use_sync_send_recv: false,
|
use_sync_send_recv: false,
|
||||||
|
@ -185,7 +187,10 @@ const etcd_tree = {
|
||||||
// number of parity chunks, required for EC
|
// number of parity chunks, required for EC
|
||||||
parity_chunks?: 1,
|
parity_chunks?: 1,
|
||||||
pg_count: 100,
|
pg_count: 100,
|
||||||
failure_domain: 'host',
|
// default is failure_domain=host
|
||||||
|
failure_domain?: 'host',
|
||||||
|
level_distribution?: { host: '123' },
|
||||||
|
pg_rules?: 'any, dc=1 host!=1, dc=1 host!=(1,2)',
|
||||||
max_osd_combinations: 10000,
|
max_osd_combinations: 10000,
|
||||||
// block_size, bitmap_granularity, immediate_commit must match all OSDs used in that pool
|
// block_size, bitmap_granularity, immediate_commit must match all OSDs used in that pool
|
||||||
block_size: 131072,
|
block_size: 131072,
|
||||||
|
@ -1089,7 +1094,6 @@ class Mon
|
||||||
pool_cfg.pg_minsize = Math.floor(pool_cfg.pg_minsize);
|
pool_cfg.pg_minsize = Math.floor(pool_cfg.pg_minsize);
|
||||||
pool_cfg.parity_chunks = Math.floor(pool_cfg.parity_chunks) || undefined;
|
pool_cfg.parity_chunks = Math.floor(pool_cfg.parity_chunks) || undefined;
|
||||||
pool_cfg.pg_count = Math.floor(pool_cfg.pg_count);
|
pool_cfg.pg_count = Math.floor(pool_cfg.pg_count);
|
||||||
pool_cfg.failure_domain = pool_cfg.failure_domain || 'host';
|
|
||||||
pool_cfg.max_osd_combinations = Math.floor(pool_cfg.max_osd_combinations) || 10000;
|
pool_cfg.max_osd_combinations = Math.floor(pool_cfg.max_osd_combinations) || 10000;
|
||||||
if (!/^[1-9]\d*$/.exec(''+pool_id))
|
if (!/^[1-9]\d*$/.exec(''+pool_id))
|
||||||
{
|
{
|
||||||
|
@ -1169,6 +1173,10 @@ class Mon
|
||||||
console.log('Pool '+pool_id+' has invalid primary_affinity_tags (must be a string or array of strings)');
|
console.log('Pool '+pool_id+' has invalid primary_affinity_tags (must be a string or array of strings)');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!this.get_pg_rules(pool_id, pool_cfg, true))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1242,6 +1250,50 @@ class Mon
|
||||||
return aff_osds;
|
return aff_osds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_pg_rules(pool_id, pool_cfg, warn)
|
||||||
|
{
|
||||||
|
if (pool_cfg.level_distribution instanceof Object)
|
||||||
|
{
|
||||||
|
const levels = this.config.placement_levels||{};
|
||||||
|
levels.host = levels.host || 100;
|
||||||
|
levels.osd = levels.osd || 101;
|
||||||
|
for (const k in pool_cfg.level_distribution)
|
||||||
|
{
|
||||||
|
if (!levels[k] || typeof pool_cfg.level_distribution[k] !== 'string' &&
|
||||||
|
(!pool_cfg.level_distribution[k] instanceof Array || pool_cfg.level_distribution[k].filter(s => typeof s !== 'string').length > 0))
|
||||||
|
{
|
||||||
|
if (warn)
|
||||||
|
console.log('Pool '+pool_id+' configuration is invalid: level_distribution should be { [level]: string or array of strings }');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parse_level_indexes(pool_cfg.level_distribution);
|
||||||
|
}
|
||||||
|
else if (typeof pool_cfg.pg_rules === 'string')
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return parse_pg_dsl(pool_cfg.pg_rules);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
if (warn)
|
||||||
|
console.log('Pool '+pool_id+' configuration is invalid: invalid pg_rules: '+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)
|
async generate_pool_pgs(pool_id, osd_tree, levels)
|
||||||
{
|
{
|
||||||
const pool_cfg = this.state.config.pools[pool_id];
|
const pool_cfg = this.state.config.pools[pool_id];
|
||||||
|
@ -1275,7 +1327,11 @@ class Mon
|
||||||
const old_pg_count = prev_pgs.length;
|
const old_pg_count = prev_pgs.length;
|
||||||
const optimize_cfg = {
|
const optimize_cfg = {
|
||||||
osd_weights: Object.values(pool_tree).filter(item => item.level === 'osd').reduce((a, c) => { a[c.id] = c.size; return a; }, {}),
|
osd_weights: Object.values(pool_tree).filter(item => item.level === 'osd').reduce((a, c) => { a[c.id] = c.size; return a; }, {}),
|
||||||
combinator: new SimpleCombinator(flatten_tree(osd_tree, levels, pool_cfg.failure_domain, 'osd'), pool_cfg.pg_size, pool_cfg.max_osd_combinations),
|
combinator: 1 || this.config.force_new_pg_combinator || !pool_cfg.level_distribution && !pool_cfg.pg_rules
|
||||||
|
// new algorithm:
|
||||||
|
? new RuleCombinator(osd_tree, this.get_pg_rules(pool_id, pool_cfg), pool_cfg.max_osd_combinations)
|
||||||
|
// old algorithm:
|
||||||
|
: new SimpleCombinator(flatten_tree(osd_tree, levels, pool_cfg.failure_domain, 'osd'), pool_cfg.pg_size, pool_cfg.max_osd_combinations),
|
||||||
pg_count: pool_cfg.pg_count,
|
pg_count: pool_cfg.pg_count,
|
||||||
pg_size: pool_cfg.pg_size,
|
pg_size: pool_cfg.pg_size,
|
||||||
pg_minsize: pool_cfg.pg_minsize,
|
pg_minsize: pool_cfg.pg_minsize,
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
const { random_custom_combinations, index_tree, parse_level_indexes, parse_pg_dsl } = require('./dsl_pgs.js');
|
||||||
|
|
||||||
|
function check(result, expected)
|
||||||
|
{
|
||||||
|
console.dir(result, { depth: null });
|
||||||
|
if (JSON.stringify(result) !== JSON.stringify(expected))
|
||||||
|
{
|
||||||
|
process.stderr.write('Unexpected value, expected: ');
|
||||||
|
console.dir(expected, { depth: null });
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check(
|
||||||
|
parse_pg_dsl("any, dc=1 host!=1, dc!=1, dc=3 host!=3, dc!=(1,3), dc=5 host!=5"),
|
||||||
|
[
|
||||||
|
[],
|
||||||
|
[ [ 'dc', '=', 1 ], [ 'host', '!=', 1 ] ],
|
||||||
|
[ [ 'dc', '!=', 1 ] ],
|
||||||
|
[ [ 'dc', '=', 3 ], [ 'host', '!=', 3 ] ],
|
||||||
|
[ [ 'dc', '!=', [ 1, 3 ] ] ],
|
||||||
|
[ [ 'dc', '=', 5 ], [ 'host', '!=', 5 ] ],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
check(
|
||||||
|
parse_pg_dsl("dc=meow, dc!=1, dc>2"),
|
||||||
|
[
|
||||||
|
[ [ 'dc', '=', { id: 'meow' } ] ],
|
||||||
|
[ [ 'dc', '!=', 1 ] ],
|
||||||
|
[ [ 'dc', '>', 2 ] ],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
check(
|
||||||
|
parse_level_indexes({ dc: '112233', host: 'ABCDEF' }),
|
||||||
|
[
|
||||||
|
[],
|
||||||
|
[ [ 'dc', '=', 1 ], [ 'host', '!=', [ 1 ] ] ],
|
||||||
|
[ [ 'dc', '!=', [ 1 ] ], [ 'host', '!=', [ 1, 2 ] ] ],
|
||||||
|
[ [ 'dc', '=', 3 ], [ 'host', '!=', [ 1, 2, 3 ] ] ],
|
||||||
|
[ [ 'dc', '!=', [ 1, 3 ] ], [ 'host', '!=', [ 1, 2, 3, 4 ] ] ],
|
||||||
|
[ [ 'dc', '=', 5 ], [ 'host', '!=', [ 1, 2, 3, 4, 5 ] ] ],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
check(
|
||||||
|
parse_level_indexes({ dc: '112233', host: 'ABCDEF' }, [ 'dc', 'host' ]),
|
||||||
|
[
|
||||||
|
[],
|
||||||
|
[ [ 'dc', '=', 1 ], [ 'host', '!=', [ 1 ] ] ],
|
||||||
|
[ [ 'dc', '!=', [ 1 ] ] ],
|
||||||
|
[ [ 'dc', '=', 3 ], [ 'host', '!=', [ 3 ] ] ],
|
||||||
|
[ [ 'dc', '!=', [ 1, 3 ] ] ],
|
||||||
|
[ [ 'dc', '=', 5 ], [ 'host', '!=', [ 5 ] ] ],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
check(
|
||||||
|
parse_level_indexes({ dc: '112211223333', host: '123456789ABC' }),
|
||||||
|
[
|
||||||
|
[],
|
||||||
|
[ [ 'dc', '=', 1 ], [ 'host', '!=', [ 1 ] ] ],
|
||||||
|
[ [ 'dc', '!=', [ 1 ] ], [ 'host', '!=', [ 1, 2 ] ] ],
|
||||||
|
[ [ 'dc', '=', 3 ], [ 'host', '!=', [ 1, 2, 3 ] ] ],
|
||||||
|
[ [ 'dc', '=', 1 ], [ 'host', '!=', [ 1, 2, 3, 4 ] ] ],
|
||||||
|
[ [ 'dc', '=', 1 ], [ 'host', '!=', [ 1, 2, 3, 4, 5 ] ] ],
|
||||||
|
[ [ 'dc', '=', 3 ], [ 'host', '!=', [ 1, 2, 3, 4, 5, 6 ] ] ],
|
||||||
|
[ [ 'dc', '=', 3 ], [ 'host', '!=', [ 1, 2, 3, 4, 5, 6, 7 ] ] ],
|
||||||
|
[ [ 'dc', '!=', [ 1, 3 ] ], [ 'host', '!=', [ 1, 2, 3, 4, 5, 6, 7, 8 ] ] ],
|
||||||
|
[ [ 'dc', '=', 9 ], [ 'host', '!=', [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ] ],
|
||||||
|
[ [ 'dc', '=', 9 ], [ 'host', '!=', [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ] ],
|
||||||
|
[ [ 'dc', '=', 9 ], [ 'host', '!=', [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ] ] ],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
check(
|
||||||
|
parse_level_indexes({ dc: '112211223333', host: '123456789ABC' }, [ 'dc', 'host' ]),
|
||||||
|
[
|
||||||
|
[],
|
||||||
|
[ [ 'dc', '=', 1 ], [ 'host', '!=', [ 1 ] ] ],
|
||||||
|
[ [ 'dc', '!=', [ 1 ] ] ],
|
||||||
|
[ [ 'dc', '=', 3 ], [ 'host', '!=', [ 3 ] ] ],
|
||||||
|
[ [ 'dc', '=', 1 ], [ 'host', '!=', [ 1, 2 ] ] ],
|
||||||
|
[ [ 'dc', '=', 1 ], [ 'host', '!=', [ 1, 2, 5 ] ] ],
|
||||||
|
[ [ 'dc', '=', 3 ], [ 'host', '!=', [ 3, 4 ] ] ],
|
||||||
|
[ [ 'dc', '=', 3 ], [ 'host', '!=', [ 3, 4, 7 ] ] ],
|
||||||
|
[ [ 'dc', '!=', [ 1, 3 ] ] ],
|
||||||
|
[ [ 'dc', '=', 9 ], [ 'host', '!=', [ 9 ] ] ],
|
||||||
|
[ [ 'dc', '=', 9 ], [ 'host', '!=', [ 9, 10 ] ] ],
|
||||||
|
[ [ 'dc', '=', 9 ], [ 'host', '!=', [ 9, 10, 11 ] ] ]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
check(
|
||||||
|
Object.keys(random_custom_combinations(index_tree([
|
||||||
|
{ id: '1', size: 1, level: 'osd' },
|
||||||
|
{ id: '2', size: 2, level: 'osd' },
|
||||||
|
{ id: '3', size: 3, level: 'osd' }
|
||||||
|
]), parse_level_indexes({ osd: '12' }), 10000)).sort(),
|
||||||
|
[ 'pg_1_2', 'pg_1_3', 'pg_2_3' ]
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('OK');
|
Loading…
Reference in New Issue