From 5b9031fecca852c090ab9b4540c82e9b538e0e08 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Sun, 7 May 2023 23:17:51 +0300 Subject: [PATCH] Fix monitor possibly applying incorrect PG history under heavy load Monitor could deceive itself by immediately saving PG configuration changes which weren't applied to etcd yet in memory, and apply incorrect PG history changes next time if the first update fails. This usually only happened under heavy load and was caught in CI. :-) --- mon/mon.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/mon/mon.js b/mon/mon.js index 01ef02f8..5a83c43a 100644 --- a/mon/mon.js +++ b/mon/mon.js @@ -956,7 +956,7 @@ class Mon return alive_set[this.rng() % alive_set.length]; } - save_new_pgs_txn(request, pool_id, up_osds, osd_tree, prev_pgs, new_pgs, pg_history) + save_new_pgs_txn(save_to, request, pool_id, up_osds, osd_tree, prev_pgs, new_pgs, pg_history) { const aff_osds = this.get_affinity_osds(this.state.config.pools[pool_id], up_osds, osd_tree); const pg_items = {}; @@ -1009,14 +1009,14 @@ class Mon }); } } - this.state.config.pgs.items = this.state.config.pgs.items || {}; + save_to.items = save_to.items || {}; if (!new_pgs.length) { - delete this.state.config.pgs.items[pool_id]; + delete save_to.items[pool_id]; } else { - this.state.config.pgs.items[pool_id] = pg_items; + save_to.items[pool_id] = pg_items; } } @@ -1160,6 +1160,7 @@ class Mon if (this.state.config.pgs.hash != tree_hash) { // Something has changed + const new_config_pgs = JSON.parse(JSON.stringify(this.state.config.pgs)); const etcd_request = { compare: [], success: [] }; for (const pool_id in (this.state.config.pgs||{}).items||{}) { @@ -1180,7 +1181,7 @@ class Mon etcd_request.success.push({ requestDeleteRange: { key: b64(this.etcd_prefix+'/pool/stats/'+pool_id), } }); - this.save_new_pgs_txn(etcd_request, pool_id, up_osds, osd_tree, prev_pgs, [], []); + this.save_new_pgs_txn(new_config_pgs, etcd_request, pool_id, up_osds, osd_tree, prev_pgs, [], []); } } for (const pool_id in this.state.config.pools) @@ -1287,14 +1288,15 @@ class Mon key: b64(this.etcd_prefix+'/pool/stats/'+pool_id), value: b64(JSON.stringify(this.state.pool.stats[pool_id])), } }); - this.save_new_pgs_txn(etcd_request, pool_id, up_osds, osd_tree, real_prev_pgs, optimize_result.int_pgs, pg_history); + this.save_new_pgs_txn(new_config_pgs, etcd_request, pool_id, up_osds, osd_tree, real_prev_pgs, optimize_result.int_pgs, pg_history); } - this.state.config.pgs.hash = tree_hash; - await this.save_pg_config(etcd_request); + new_config_pgs.hash = tree_hash; + await this.save_pg_config(new_config_pgs, etcd_request); } else { // Nothing changed, but we still want to recheck the distribution of primaries + let new_config_pgs; let changed = false; for (const pool_id in this.state.config.pools) { @@ -1314,31 +1316,35 @@ class Mon const new_primary = this.pick_primary(pool_id, pg_cfg.osd_set, up_osds, aff_osds); if (pg_cfg.primary != new_primary) { + if (!new_config_pgs) + { + new_config_pgs = JSON.parse(JSON.stringify(this.state.config.pgs)); + } console.log( `Moving pool ${pool_id} (${pool_cfg.name || 'unnamed'}) PG ${pg_num}`+ ` primary OSD from ${pg_cfg.primary} to ${new_primary}` ); changed = true; - pg_cfg.primary = new_primary; + new_config_pgs.items[pool_id][pg_num].primary = new_primary; } } } } if (changed) { - await this.save_pg_config(); + await this.save_pg_config(new_config_pgs); } } } - async save_pg_config(etcd_request = { compare: [], success: [] }) + async save_pg_config(new_config_pgs, etcd_request = { compare: [], success: [] }) { etcd_request.compare.push( { key: b64(this.etcd_prefix+'/mon/master'), target: 'LEASE', lease: ''+this.etcd_lease_id }, { key: b64(this.etcd_prefix+'/config/pgs'), target: 'MOD', mod_revision: ''+this.etcd_watch_revision, result: 'LESS' }, ); etcd_request.success.push( - { requestPut: { key: b64(this.etcd_prefix+'/config/pgs'), value: b64(JSON.stringify(this.state.config.pgs)) } }, + { requestPut: { key: b64(this.etcd_prefix+'/config/pgs'), value: b64(JSON.stringify(new_config_pgs)) } }, ); const res = await this.etcd_call('/kv/txn', etcd_request, this.config.etcd_mon_timeout, 0); if (!res.succeeded)