This greatly speeds up PG placement and makes it more uniform both because the LP task becomes simpler and because the distribution of individual OSDs is optimised manually
216 lines
6.9 KiB
JavaScript
216 lines
6.9 KiB
JavaScript
// Copyright (c) Vitaliy Filippov, 2019+
|
|
// License: VNPL-1.1 (see README.md for details)
|
|
|
|
function get_osd_tree(global_config, state)
|
|
{
|
|
const levels = global_config.placement_levels||{};
|
|
levels.host = levels.host || 100;
|
|
levels.osd = levels.osd || 101;
|
|
const tree = {};
|
|
let up_osds = {};
|
|
// This requires monitor system time to be in sync with OSD system times (at least to some extent)
|
|
const down_time = Date.now()/1000 - global_config.osd_out_time;
|
|
for (const osd_num of Object.keys(state.osd.stats).sort((a, b) => a - b))
|
|
{
|
|
const stat = state.osd.stats[osd_num];
|
|
const osd_cfg = state.config.osd[osd_num];
|
|
let reweight = osd_cfg == null ? 1 : Number(osd_cfg.reweight);
|
|
if (isNaN(reweight) || reweight < 0 || reweight > 0)
|
|
reweight = 1;
|
|
if (stat && stat.size && reweight && (state.osd.state[osd_num] || Number(stat.time) >= down_time ||
|
|
osd_cfg && osd_cfg.noout))
|
|
{
|
|
// Numeric IDs are reserved for OSDs
|
|
if (state.osd.state[osd_num] && reweight > 0)
|
|
{
|
|
// React to down OSDs immediately
|
|
up_osds[osd_num] = true;
|
|
}
|
|
tree[osd_num] = tree[osd_num] || {};
|
|
tree[osd_num].id = osd_num;
|
|
tree[osd_num].parent = tree[osd_num].parent || stat.host;
|
|
tree[osd_num].level = 'osd';
|
|
tree[osd_num].size = reweight * stat.size / 1024 / 1024 / 1024 / 1024; // terabytes
|
|
if (osd_cfg && osd_cfg.tags)
|
|
{
|
|
tree[osd_num].tags = (osd_cfg.tags instanceof Array ? [ ...osd_cfg.tags ] : [ osd_cfg.tags ])
|
|
.reduce((a, c) => { a[c] = true; return a; }, {});
|
|
}
|
|
delete tree[osd_num].children;
|
|
if (!tree[stat.host])
|
|
{
|
|
tree[stat.host] = {
|
|
id: stat.host,
|
|
level: 'host',
|
|
parent: null,
|
|
children: [],
|
|
};
|
|
}
|
|
}
|
|
}
|
|
for (const node_id in state.config.node_placement||{})
|
|
{
|
|
const node_cfg = state.config.node_placement[node_id];
|
|
if (/^\d+$/.exec(node_id))
|
|
{
|
|
node_cfg.level = 'osd';
|
|
}
|
|
if (!node_id || !node_cfg.level || !levels[node_cfg.level] ||
|
|
node_cfg.level === 'osd' && !tree[node_id])
|
|
{
|
|
// All nodes must have non-empty IDs and valid levels
|
|
// OSDs have to actually exist
|
|
continue;
|
|
}
|
|
tree[node_id] = tree[node_id] || {};
|
|
tree[node_id].id = node_id;
|
|
tree[node_id].level = node_cfg.level;
|
|
tree[node_id].parent = node_cfg.parent;
|
|
if (node_cfg.level !== 'osd')
|
|
{
|
|
tree[node_id].children = [];
|
|
}
|
|
}
|
|
return { up_osds, levels, osd_tree: tree };
|
|
}
|
|
|
|
function make_hier_tree(global_config, tree)
|
|
{
|
|
const levels = global_config.placement_levels||{};
|
|
levels.host = levels.host || 100;
|
|
levels.osd = levels.osd || 101;
|
|
tree = { ...tree };
|
|
for (const node_id in tree)
|
|
{
|
|
tree[node_id] = { ...tree[node_id], children: [] };
|
|
}
|
|
tree[''] = { children: [] };
|
|
for (const node_id in tree)
|
|
{
|
|
if (node_id === '' || !(tree[node_id].children||[]).length && (tree[node_id].size||0) <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
const node_cfg = tree[node_id];
|
|
const node_level = levels[node_cfg.level] || node_cfg.level;
|
|
let parent_level = node_cfg.parent && tree[node_cfg.parent] && tree[node_cfg.parent].children
|
|
&& tree[node_cfg.parent].level;
|
|
parent_level = parent_level ? (levels[parent_level] || parent_level) : null;
|
|
// Parent's level must be less than child's; OSDs must be leaves
|
|
const parent = parent_level && parent_level < node_level ? node_cfg.parent : '';
|
|
tree[parent].children.push(tree[node_id]);
|
|
}
|
|
// Delete empty nodes
|
|
let deleted = 0;
|
|
do
|
|
{
|
|
deleted = 0;
|
|
for (const node_id in tree)
|
|
{
|
|
if (!(tree[node_id].children||[]).length && (tree[node_id].size||0) <= 0)
|
|
{
|
|
const parent = tree[node_id].parent;
|
|
if (parent)
|
|
{
|
|
tree[parent].children = tree[parent].children.filter(c => c != tree[node_id]);
|
|
}
|
|
deleted++;
|
|
delete tree[node_id];
|
|
}
|
|
}
|
|
} while (deleted > 0);
|
|
return tree;
|
|
}
|
|
|
|
function filter_osds_by_root_node(global_config, pool_tree, root_node)
|
|
{
|
|
if (!root_node)
|
|
{
|
|
return;
|
|
}
|
|
let hier_tree = make_hier_tree(global_config, pool_tree);
|
|
let included = [ ...(hier_tree[root_node] || {}).children||[] ];
|
|
for (let i = 0; i < included.length; i++)
|
|
{
|
|
if (included[i].children)
|
|
{
|
|
included.splice(i+1, 0, ...included[i].children);
|
|
}
|
|
}
|
|
let cur = pool_tree[root_node] || {};
|
|
while (cur && cur.id)
|
|
{
|
|
included.unshift(cur);
|
|
cur = pool_tree[cur.parent||''];
|
|
}
|
|
included = included.reduce((a, c) => { a[c.id||''] = true; return a; }, {});
|
|
for (const item in pool_tree)
|
|
{
|
|
if (!included[item])
|
|
{
|
|
delete pool_tree[item];
|
|
}
|
|
}
|
|
}
|
|
|
|
function filter_osds_by_tags(orig_tree, tags)
|
|
{
|
|
if (!tags)
|
|
{
|
|
return;
|
|
}
|
|
for (const tag of (tags instanceof Array ? tags : [ tags ]))
|
|
{
|
|
for (const osd in orig_tree)
|
|
{
|
|
if (orig_tree[osd].level === 'osd' &&
|
|
(!orig_tree[osd].tags || !orig_tree[osd].tags[tag]))
|
|
{
|
|
delete orig_tree[osd];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function filter_osds_by_block_layout(orig_tree, osd_stats, block_size, bitmap_granularity, immediate_commit)
|
|
{
|
|
for (const osd in orig_tree)
|
|
{
|
|
if (orig_tree[osd].level === 'osd')
|
|
{
|
|
const osd_stat = osd_stats[osd];
|
|
if (osd_stat && (osd_stat.bs_block_size && osd_stat.bs_block_size != block_size ||
|
|
osd_stat.bitmap_granularity && osd_stat.bitmap_granularity != bitmap_granularity ||
|
|
osd_stat.immediate_commit == 'small' && immediate_commit == 'all' ||
|
|
osd_stat.immediate_commit == 'none' && immediate_commit != 'none'))
|
|
{
|
|
delete orig_tree[osd];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function get_affinity_osds(pool_cfg, up_osds, osd_tree)
|
|
{
|
|
let aff_osds = up_osds;
|
|
if (pool_cfg.primary_affinity_tags)
|
|
{
|
|
aff_osds = Object.keys(up_osds).reduce((a, c) => { a[c] = osd_tree[c]; return a; }, {});
|
|
filter_osds_by_tags(aff_osds, pool_cfg.primary_affinity_tags);
|
|
for (const osd in aff_osds)
|
|
{
|
|
aff_osds[osd] = true;
|
|
}
|
|
}
|
|
return aff_osds;
|
|
}
|
|
|
|
module.exports = {
|
|
get_osd_tree,
|
|
make_hier_tree,
|
|
filter_osds_by_root_node,
|
|
filter_osds_by_tags,
|
|
filter_osds_by_block_layout,
|
|
get_affinity_osds,
|
|
};
|