Skip to content

Commit 0bf3bf6

Browse files
authored
system store - endpoints load from core instead of db (#9150)
* system store - endpoints load from core instead of db Signed-off-by: Amit Prinz Setter <[email protected]>
1 parent d1fb324 commit 0bf3bf6

File tree

8 files changed

+141
-16
lines changed

8 files changed

+141
-16
lines changed

config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,9 @@ config.INTERNAL_STORAGE_POOL_NAME = 'system-internal-storage-pool';
255255
config.ALLOW_BUCKET_CREATE_ON_INTERNAL = true;
256256
config.BUCKET_AUTOCONF_TIER2_ENABLED = false;
257257
config.SYSTEM_STORE_LOAD_CONCURRENCY = parseInt(process.env.SYSTEM_STORE_LOAD_CONCURRENCY, 10) || 5;
258-
258+
// SYSTEM_STORE_SOURCE determines the preffered source for loading system_store data
259+
// This can be either "DB" to load from the DB or "CORE" to load from the system_server in noobaa-core
260+
config.SYSTEM_STORE_SOURCE = process.env.SYSTEM_STORE_SOURCE?.toUpperCase() || "DB";
259261
//////////////////////////
260262
// MD AGGREGATOR CONFIG //
261263
//////////////////////////

src/api/server_inter_process_api.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ module.exports = {
1717
params: {
1818
type: 'object',
1919
properties: {
20-
since: { idate: true }
20+
since: { idate: true },
21+
load_source: {
22+
type: 'string',
23+
enum: ['DB', 'CORE']
24+
}
2125
}
2226
},
2327
auth: {

src/api/system_api.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,19 @@ module.exports = {
459459
auth: {
460460
system: 'admin'
461461
}
462+
},
463+
464+
get_system_store: {
465+
method: 'GET',
466+
reply: {
467+
type: 'object',
468+
properties: {
469+
// [RPC_BUFFERS].data
470+
},
471+
},
472+
auth: {
473+
system: false
474+
}
462475
}
463476
},
464477

src/server/common_services/server_inter_process.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ const dbg = require('../../util/debug_module')(__filename);
1313
const system_store = require('../system_services/system_store').get_instance();
1414
const server_rpc = require('../server_rpc');
1515

16-
1716
/**
1817
*
1918
*/
2019
async function load_system_store(req) {
2120
await system_store.load(
22-
req && req.rpc_params && req.rpc_params.since
21+
req?.rpc_params?.since,
22+
req?.rpc_params?.load_source.toUpperCase()
2323
);
2424
}
2525

src/server/system_services/system_server.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const config = require('../../../config');
1919
const { BucketStatsStore } = require('../analytic_services/bucket_stats_store');
2020
const { EndpointStatsStore } = require('../analytic_services/endpoint_stats_store');
2121
const os_utils = require('../../util/os_utils');
22-
const { RpcError } = require('../../rpc');
22+
const { RpcError, RPC_BUFFERS } = require('../../rpc');
2323
const nb_native = require('../../util/nb_native');
2424
const Dispatcher = require('../notifications/dispatcher');
2525
const size_utils = require('../../util/size_utils');
@@ -298,6 +298,15 @@ function get_system_status(req) {
298298
};
299299
}
300300

301+
async function get_system_store() {
302+
try {
303+
return {
304+
[RPC_BUFFERS]: {data: Buffer.from(JSON.stringify(await system_store.recent_db_data()))},
305+
};
306+
} catch (e) {
307+
dbg.error("Failed getting system store", e);
308+
}
309+
}
301310

302311
async function _update_system_state(system_id, mode) {
303312
const update = {
@@ -1595,3 +1604,5 @@ exports.rotate_master_key = rotate_master_key;
15951604
exports.disable_master_key = disable_master_key;
15961605
exports.enable_master_key = enable_master_key;
15971606
exports.upgrade_master_keys = upgrade_master_keys;
1607+
1608+
exports.get_system_store = get_system_store;

src/server/system_services/system_store.js

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ const size_utils = require('../../util/size_utils');
3838
const os_utils = require('../../util/os_utils');
3939
const config = require('../../../config');
4040
const db_client = require('../../util/db_client');
41+
const { decode_json } = require('../../util/postgres_client');
4142

42-
const { RpcError } = require('../../rpc');
43+
const { RpcError, RPC_BUFFERS } = require('../../rpc');
4344
const master_key_manager = require('./master_key_manager');
4445

4546
const COLLECTIONS = [{
@@ -152,6 +153,10 @@ const COLLECTIONS_BY_NAME = _.keyBy(COLLECTIONS, 'name');
152153

153154
const accounts_by_email_lowercase = [];
154155

156+
const SOURCE = Object.freeze({
157+
DB: 'DB',
158+
CORE: 'CORE',
159+
});
155160

156161
/**
157162
*
@@ -352,9 +357,14 @@ class SystemStore extends EventEmitter {
352357
this.START_REFRESH_THRESHOLD = 10 * 60 * 1000;
353358
this.FORCE_REFRESH_THRESHOLD = 60 * 60 * 1000;
354359
this.SYSTEM_STORE_LOAD_CONCURRENCY = config.SYSTEM_STORE_LOAD_CONCURRENCY || 5;
360+
this.source = options.source || config.SYSTEM_STORE_SOURCE;
361+
this.source = this.source.toUpperCase();
362+
dbg.log0("system store source is", this.source);
355363
this._load_serial = new semaphore.Semaphore(1, { warning_timeout: this.START_REFRESH_THRESHOLD });
356-
for (const col of COLLECTIONS) {
357-
db_client.instance().define_collection(col);
364+
if (options.skip_define_for_tests !== true) {
365+
for (const col of COLLECTIONS) {
366+
db_client.instance().define_collection(col);
367+
}
358368
}
359369
js_utils.deep_freeze(COLLECTIONS);
360370
js_utils.deep_freeze(COLLECTIONS_BY_NAME);
@@ -407,14 +417,21 @@ class SystemStore extends EventEmitter {
407417
}
408418
}
409419

410-
async load(since) {
420+
async load(since, load_source) {
421+
//if endpoints load from core, and this load is for core
422+
//(ie, the first load_system_store() out of two with load_source === 'CORE'),
423+
//then endpoints skip it.
424+
//endpoints will be updated in the next load_system_store()
425+
//once core's in memory system store is updated.
426+
if (load_source && (this.source !== load_source)) {
427+
return;
428+
}
429+
411430
// serializing load requests since we have to run a fresh load after the previous one will finish
412431
// because it might not see the latest changes if we don't reload right after make_changes.
413432
return this._load_serial.surround(async () => {
414433
try {
415-
dbg.log3('SystemStore: loading ... this.last_update_time =', this.last_update_time, ", since =", since);
416-
417-
const new_data = new SystemStoreData();
434+
dbg.log3('SystemStore: loading ... this.last_update_time =', this.last_update_time, ", since =", since, "load_source =", load_source);
418435

419436
// If we get a load request with an timestamp older then our last update time
420437
// we ensure we load everyting from that timestamp by updating our last_update_time.
@@ -423,9 +440,25 @@ class SystemStore extends EventEmitter {
423440
this.last_update_time = since;
424441
}
425442
this.master_key_manager.load_root_key();
443+
const new_data = new SystemStoreData();
426444
let millistamp = time_utils.millistamp();
427445
await this._register_for_changes();
428-
await this._read_new_data_from_db(new_data);
446+
let from_core_failure = false;
447+
448+
if (this.source === SOURCE.CORE) {
449+
try {
450+
this.data = new SystemStoreData();
451+
await this._read_new_data_from_core(this.data);
452+
} catch (e) {
453+
dbg.error("Failed to load system store from core. Will load from db.", e);
454+
from_core_failure = true;
455+
}
456+
}
457+
458+
if (this.source === SOURCE.DB || from_core_failure) {
459+
await this._read_new_data_from_db(new_data);
460+
}
461+
429462
const secret = await os_utils.read_server_secret();
430463
this._server_secret = secret;
431464
if (dbg.should_log(1)) { //param should match below logs' level
@@ -435,8 +468,10 @@ class SystemStore extends EventEmitter {
435468
depth: 4
436469
}));
437470
}
438-
this.old_db_data = this._update_data_from_new(this.old_db_data || {}, new_data);
439-
this.data = _.cloneDeep(this.old_db_data);
471+
if (this.source === SOURCE.DB || from_core_failure) {
472+
this.old_db_data = this._update_data_from_new(this.old_db_data || {}, new_data);
473+
this.data = _.cloneDeep(this.old_db_data);
474+
}
440475
millistamp = time_utils.millistamp();
441476
this.data.rebuild();
442477
dbg.log1('SystemStore: rebuild took', time_utils.millitook(millistamp));
@@ -458,6 +493,14 @@ class SystemStore extends EventEmitter {
458493
});
459494
}
460495

496+
//return the latest copy of in-memory data
497+
async recent_db_data() {
498+
if (this.source === SOURCE.CORE) {
499+
throw new RpcError('BAD_REQUEST', 'recent_db_data is not available for CORE source');
500+
}
501+
return this._load_serial.surround(async () => this.old_db_data);
502+
}
503+
461504
_update_data_from_new(data, new_data) {
462505
COLLECTIONS.forEach(col => {
463506
const old_items = data[col.name];
@@ -523,6 +566,28 @@ class SystemStore extends EventEmitter {
523566
this.last_update_time = now;
524567
}
525568

569+
async _read_new_data_from_core(target) {
570+
dbg.log3("_read_new_data_from_core begins");
571+
const res = await server_rpc.client.system.get_system_store();
572+
const ss = JSON.parse(res[RPC_BUFFERS].data.toString());
573+
dbg.log3("_read_new_data_from_core new system store", ss);
574+
for (const key of Object.keys(ss)) {
575+
const collection = COLLECTIONS_BY_NAME[key];
576+
if (collection) {
577+
target[key] = [];
578+
_.each(ss[key], item => {
579+
//these two lines will transform string values into appropriately typed objects
580+
//(SensitiveString, ObjectId) according to schema
581+
const after = decode_json(collection.schema, item);
582+
db_client.instance().validate(key, after);
583+
target[key].push(after);
584+
});
585+
} else {
586+
target[key] = ss[key];
587+
}
588+
}
589+
}
590+
526591
_check_schema(col, item, warn) {
527592
return db_client.instance().validate(col.name, item, warn);
528593
}
@@ -616,12 +681,25 @@ class SystemStore extends EventEmitter {
616681
if (this.is_standalone) {
617682
await this.load(last_update);
618683
} else if (publish) {
684+
dbg.log2("first phase publish");
619685
// notify all the cluster (including myself) to reload
620686
await server_rpc.client.redirector.publish_to_cluster({
621687
method_api: 'server_inter_process_api',
622688
method_name: 'load_system_store',
623689
target: '',
624-
request_params: { since: last_update }
690+
request_params: { since: last_update, load_source: SOURCE.DB }
691+
});
692+
693+
//if endpoints are loading system store from core, we need to wait until
694+
//above publish_to_cluster() will update core's in-memory db.
695+
//the next publist_to_cluster() will make endpoints load the updated
696+
//system store from core
697+
dbg.log2("second phase publish");
698+
await server_rpc.client.redirector.publish_to_cluster({
699+
method_api: 'server_inter_process_api',
700+
method_name: 'load_system_store',
701+
target: '',
702+
request_params: { since: last_update, load_source: SOURCE.CORE }
625703
});
626704
}
627705
}
@@ -851,3 +929,4 @@ SystemStore._instance = undefined;
851929
// EXPORTS
852930
exports.SystemStore = SystemStore;
853931
exports.get_instance = SystemStore.get_instance;
932+
exports.SOURCE = SOURCE;

src/test/integration_tests/db/test_system_store.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const mocha = require('mocha');
55
const assert = require('assert');
66
const coretest = require('../../utils/coretest/coretest');
77
const db_client = require('../../../util/db_client');
8+
const { SystemStore, SOURCE } = require('../../../server/system_services/system_store');
89

910
// setup coretest first to prepare the env
1011
coretest.setup();
@@ -138,4 +139,18 @@ mocha.describe('system_store', function() {
138139
assert.strictEqual(data2.systems[0].name, 'new_name');
139140
});
140141

142+
mocha.it("Load from core", async function() {
143+
144+
const system_store_from_core = new SystemStore({
145+
source: SOURCE.CORE,
146+
skip_define_for_tests: true
147+
});
148+
149+
const from_db = await system_store.load();
150+
const from_core = await system_store_from_core.load(undefined, SOURCE.CORE);
151+
152+
assert.deepStrictEqual(from_db.data, from_core.data);
153+
154+
});
155+
141156
});

src/util/postgres_client.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1951,3 +1951,4 @@ PostgresClient._instance = undefined;
19511951
// EXPORTS
19521952
exports.PostgresClient = PostgresClient;
19531953
exports.instance = PostgresClient.instance;
1954+
exports.decode_json = decode_json;

0 commit comments

Comments
 (0)