@abtnode/core 1.8.56 → 1.8.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/blocklet/manager/disk.js +78 -18
- package/lib/event.js +2 -2
- package/lib/index.js +21 -19
- package/lib/router/helper.js +3 -2
- package/lib/states/audit-log.js +2 -0
- package/lib/states/blocklet-extras.js +30 -0
- package/lib/states/node.js +1 -1
- package/lib/util/blocklet.js +22 -0
- package/lib/util/maintain.js +233 -0
- package/lib/validators/blocklet-extra.js +24 -0
- package/lib/validators/blocklet.js +1 -10
- package/lib/validators/util.js +21 -5
- package/package.json +19 -17
- package/lib/util/upgrade.js +0 -175
|
@@ -13,7 +13,7 @@ const { Throttle } = require('stream-throttle');
|
|
|
13
13
|
const LRU = require('lru-cache');
|
|
14
14
|
const joi = require('joi');
|
|
15
15
|
const isUrl = require('is-url');
|
|
16
|
-
const { isNFTExpired } = require('@abtnode/util/lib/nft');
|
|
16
|
+
const { isNFTExpired, getNftExpirationDate } = require('@abtnode/util/lib/nft');
|
|
17
17
|
const didDocument = require('@abtnode/util/lib/did-document');
|
|
18
18
|
const { sign } = require('@arcblock/jwt');
|
|
19
19
|
const { isValid: isValidDid } = require('@arcblock/did');
|
|
@@ -2659,6 +2659,11 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2659
2659
|
time: '0 */30 * * * *', // 30min
|
|
2660
2660
|
fn: () => this._deleteExpiredExternalBlocklet(),
|
|
2661
2661
|
},
|
|
2662
|
+
{
|
|
2663
|
+
name: 'clean-expired-blocklet-data',
|
|
2664
|
+
time: '0 */20 0 * * *', // 每天凌晨 0 点的每 20 分钟
|
|
2665
|
+
fn: () => this._cleanExpiredBlockletData(),
|
|
2666
|
+
},
|
|
2662
2667
|
{
|
|
2663
2668
|
name: 'record-blocklet-runtime-history',
|
|
2664
2669
|
time: `*/${MONITOR_RECORD_INTERVAL_SEC} * * * * *`, // 10s
|
|
@@ -2734,6 +2739,8 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2734
2739
|
|
|
2735
2740
|
await validateBlocklet(blocklet);
|
|
2736
2741
|
|
|
2742
|
+
await states.blockletExtras.addMeta({ did, meta: { did, name }, controller });
|
|
2743
|
+
|
|
2737
2744
|
await this._setConfigsFromMeta(did);
|
|
2738
2745
|
|
|
2739
2746
|
logger.info('blocklet added to database', { did: meta.did });
|
|
@@ -3607,8 +3614,6 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3607
3614
|
async _deleteBlocklet({ did, keepData, keepLogsDir, keepConfigs }, context) {
|
|
3608
3615
|
const blocklet = await states.blocklet.getBlocklet(did);
|
|
3609
3616
|
const { name } = blocklet.meta;
|
|
3610
|
-
const dataDir = path.join(this.dataDirs.data, name);
|
|
3611
|
-
const logsDir = path.join(this.dataDirs.logs, name);
|
|
3612
3617
|
const cacheDir = path.join(this.dataDirs.cache, name);
|
|
3613
3618
|
|
|
3614
3619
|
// Cleanup db
|
|
@@ -3616,19 +3621,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3616
3621
|
|
|
3617
3622
|
// Cleanup disk storage
|
|
3618
3623
|
fs.removeSync(cacheDir);
|
|
3619
|
-
|
|
3620
|
-
fs.removeSync(dataDir);
|
|
3621
|
-
fs.removeSync(logsDir);
|
|
3622
|
-
await states.blockletExtras.remove({ did: blocklet.meta.did });
|
|
3623
|
-
} else {
|
|
3624
|
-
if (keepLogsDir === false) {
|
|
3625
|
-
fs.removeSync(logsDir);
|
|
3626
|
-
}
|
|
3627
|
-
|
|
3628
|
-
if (keepConfigs === false) {
|
|
3629
|
-
await states.blockletExtras.remove({ did: blocklet.meta.did });
|
|
3630
|
-
}
|
|
3631
|
-
}
|
|
3624
|
+
await this._cleanBlockletData({ blocklet, keepData, keepLogsDir, keepConfigs });
|
|
3632
3625
|
|
|
3633
3626
|
const nodeInfo = await states.node.read();
|
|
3634
3627
|
const { wallet } = getBlockletInfo(blocklet, nodeInfo.sk);
|
|
@@ -3661,6 +3654,36 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3661
3654
|
return blocklet;
|
|
3662
3655
|
}
|
|
3663
3656
|
|
|
3657
|
+
async _cleanBlockletData({ blocklet, keepData, keepLogsDir, keepConfigs }) {
|
|
3658
|
+
const { name } = blocklet.meta;
|
|
3659
|
+
|
|
3660
|
+
const dataDir = path.join(this.dataDirs.data, name);
|
|
3661
|
+
const logsDir = path.join(this.dataDirs.logs, name);
|
|
3662
|
+
|
|
3663
|
+
logger.info(`clean blocklet ${blocklet.meta.did} data`, { keepData, keepLogsDir, keepConfigs });
|
|
3664
|
+
|
|
3665
|
+
if (keepData === false) {
|
|
3666
|
+
fs.removeSync(dataDir);
|
|
3667
|
+
logger.info(`removed blocklet ${blocklet.meta.did} data dir: ${dataDir}`);
|
|
3668
|
+
|
|
3669
|
+
fs.removeSync(logsDir);
|
|
3670
|
+
logger.info(`removed blocklet ${blocklet.meta.did} logs dir: ${logsDir}`);
|
|
3671
|
+
|
|
3672
|
+
await states.blockletExtras.remove({ did: blocklet.meta.did });
|
|
3673
|
+
logger.info(`removed blocklet ${blocklet.meta.did} extra data`);
|
|
3674
|
+
} else {
|
|
3675
|
+
if (keepLogsDir === false) {
|
|
3676
|
+
fs.removeSync(logsDir);
|
|
3677
|
+
logger.info(`removed blocklet ${blocklet.meta.did} logs dir: ${logsDir}`);
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
if (keepConfigs === false) {
|
|
3681
|
+
await states.blockletExtras.remove({ did: blocklet.meta.did });
|
|
3682
|
+
logger.info(`removed blocklet ${blocklet.meta.did} extra data`);
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3664
3687
|
async _setConfigsFromMeta(did, childDid) {
|
|
3665
3688
|
const blocklet = await getBlocklet({ states, dataDirs: this.dataDirs, did, validateEnv: false, ensureDirs: false });
|
|
3666
3689
|
|
|
@@ -3846,11 +3869,19 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3846
3869
|
nftId: blocklet.controller.nftId,
|
|
3847
3870
|
});
|
|
3848
3871
|
|
|
3849
|
-
|
|
3850
|
-
await this.delete({ did: blocklet.meta.did, keepData: false, keepConfigs: false, keepLogsDir: false });
|
|
3872
|
+
await this.delete({ did: blocklet.meta.did, keepData: true, keepConfigs: true, keepLogsDir: true });
|
|
3851
3873
|
logger.info('the expired blocklet already deleted', {
|
|
3852
3874
|
blockletId: blocklet._id,
|
|
3853
3875
|
nftId: blocklet.controller.nftId,
|
|
3876
|
+
did: blocklet.meta.did,
|
|
3877
|
+
});
|
|
3878
|
+
|
|
3879
|
+
const expiredAt = getNftExpirationDate(assetState);
|
|
3880
|
+
await states.blockletExtras.updateExpireInfo({ did: blocklet.meta.did, expiredAt });
|
|
3881
|
+
logger.info('updated expired blocklet extra info', {
|
|
3882
|
+
blockletId: blocklet._id,
|
|
3883
|
+
nftId: blocklet.controller.nftId,
|
|
3884
|
+
did: blocklet.meta.did,
|
|
3854
3885
|
});
|
|
3855
3886
|
}
|
|
3856
3887
|
} catch (error) {
|
|
@@ -3869,6 +3900,35 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
3869
3900
|
}
|
|
3870
3901
|
}
|
|
3871
3902
|
|
|
3903
|
+
async _cleanExpiredBlockletData() {
|
|
3904
|
+
try {
|
|
3905
|
+
logger.info('start clean expired blocklet data');
|
|
3906
|
+
const blockletExtras = await states.blockletExtras.getExpiredList();
|
|
3907
|
+
if (blockletExtras.length === 0) {
|
|
3908
|
+
logger.info('no expired blocklet data');
|
|
3909
|
+
return;
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
const tasks = blockletExtras.map(async ({ did }) => {
|
|
3913
|
+
const blocklet = await states.blocklet.getBlocklet(did);
|
|
3914
|
+
await this._cleanBlockletData({ blocklet, keepData: false, keepLogsDir: false, keepConfigs: false });
|
|
3915
|
+
|
|
3916
|
+
this.emit(BlockletEvents.dataCleaned, {
|
|
3917
|
+
blocklet,
|
|
3918
|
+
keepRouting: false,
|
|
3919
|
+
});
|
|
3920
|
+
|
|
3921
|
+
logger.info(`cleaned expired blocklet blocklet ${did} data`);
|
|
3922
|
+
});
|
|
3923
|
+
|
|
3924
|
+
await Promise.all(tasks);
|
|
3925
|
+
|
|
3926
|
+
logger.info('clean expired blocklet data done');
|
|
3927
|
+
} catch (error) {
|
|
3928
|
+
logger.error('clean expired blocklet data failed', { error });
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3872
3932
|
async _updateDidDocument(blocklet) {
|
|
3873
3933
|
const nodeInfo = await states.node.read();
|
|
3874
3934
|
|
package/lib/event.js
CHANGED
|
@@ -203,7 +203,7 @@ module.exports = ({
|
|
|
203
203
|
logger.error('Failed to createAuditLog for upgradeBlocklet', { error });
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
|
-
} else if ([BlockletEvents.removed].includes(eventName)) {
|
|
206
|
+
} else if ([BlockletEvents.removed, BlockletEvents.dataCleaned].includes(eventName)) {
|
|
207
207
|
await handleBlockletRemove(eventName, payload);
|
|
208
208
|
} else if ([BlockletEvents.started].includes(eventName)) {
|
|
209
209
|
const { publicToStore } = blocklet.settings || {};
|
|
@@ -306,7 +306,7 @@ module.exports = ({
|
|
|
306
306
|
}
|
|
307
307
|
});
|
|
308
308
|
|
|
309
|
-
listen(nodeState, EVENTS.
|
|
309
|
+
listen(nodeState, EVENTS.NODE_MAINTAIN_PROGRESS, onEvent);
|
|
310
310
|
nodeState.on(EVENTS.RELOAD_GATEWAY, (nodeInfo) => {
|
|
311
311
|
handleRouting(nodeInfo).catch((err) => {
|
|
312
312
|
logger.error('Handle routing failed on node.updated', { error: err });
|
package/lib/index.js
CHANGED
|
@@ -22,7 +22,7 @@ const Cert = require('./cert');
|
|
|
22
22
|
|
|
23
23
|
const IP = require('./util/ip');
|
|
24
24
|
const DomainStatus = require('./util/domain-status');
|
|
25
|
-
const
|
|
25
|
+
const Maintain = require('./util/maintain');
|
|
26
26
|
const resetNode = require('./util/reset-node');
|
|
27
27
|
const DiskMonitor = require('./util/disk-monitor');
|
|
28
28
|
const StoreUtil = require('./util/store');
|
|
@@ -30,6 +30,7 @@ const createQueue = require('./queue');
|
|
|
30
30
|
const createEvents = require('./event');
|
|
31
31
|
const pm2Events = require('./blocklet/manager/pm2-events');
|
|
32
32
|
const { createStateReadyQueue, createStateReadyHandler } = require('./util/ready');
|
|
33
|
+
const { createDataArchive } = require('./util/blocklet');
|
|
33
34
|
const { toStatus, fromStatus, ensureDataDirs, getQueueConcurrencyByMem, getStateCrons } = require('./util');
|
|
34
35
|
|
|
35
36
|
/**
|
|
@@ -237,7 +238,7 @@ function ABTNode(options) {
|
|
|
237
238
|
getNodeEnv: nodeAPI.getEnv.bind(nodeAPI),
|
|
238
239
|
updateNodeInfo: nodeAPI.updateNodeInfo.bind(nodeAPI),
|
|
239
240
|
getDelegationState: nodeAPI.getDelegationState.bind(nodeAPI),
|
|
240
|
-
|
|
241
|
+
cleanupDirtyMaintainState: states.node.cleanupDirtyMaintainState.bind(states.node),
|
|
241
242
|
updateNodeOwner: states.node.updateNodeOwner.bind(states.node),
|
|
242
243
|
updateNftHolder: states.node.updateNftHolder.bind(states.node),
|
|
243
244
|
updateNodeRouting,
|
|
@@ -369,10 +370,11 @@ function ABTNode(options) {
|
|
|
369
370
|
markMigrationExecuted: states.migration.markExecuted.bind(states.migration),
|
|
370
371
|
|
|
371
372
|
// Upgrading
|
|
372
|
-
upgradeNodeVersion:
|
|
373
|
-
checkNodeVersion:
|
|
374
|
-
|
|
375
|
-
|
|
373
|
+
upgradeNodeVersion: () => Maintain.triggerMaintain({ action: 'upgrade', next: Maintain.resumeMaintain }),
|
|
374
|
+
checkNodeVersion: Maintain.checkNewVersion,
|
|
375
|
+
restartServer: () => Maintain.triggerMaintain({ action: 'restart', next: Maintain.resumeMaintain }),
|
|
376
|
+
isBeingMaintained: Maintain.isBeingMaintained,
|
|
377
|
+
resumeMaintain: Maintain.resumeMaintain,
|
|
376
378
|
|
|
377
379
|
// Session
|
|
378
380
|
getSession: (params, context) => states.session.read(params.id, context),
|
|
@@ -390,6 +392,9 @@ function ABTNode(options) {
|
|
|
390
392
|
getNodeRuntimeInfo: nodeAPI.getRealtimeData.bind(nodeAPI),
|
|
391
393
|
|
|
392
394
|
getRouterProvider,
|
|
395
|
+
|
|
396
|
+
// for exporting blocklet data dir
|
|
397
|
+
createBlockletDataArchive: createDataArchive,
|
|
393
398
|
};
|
|
394
399
|
|
|
395
400
|
const events = createEvents({
|
|
@@ -415,7 +420,7 @@ function ABTNode(options) {
|
|
|
415
420
|
context: { states, events, webhook },
|
|
416
421
|
jobs: [
|
|
417
422
|
IP.cron,
|
|
418
|
-
|
|
423
|
+
Maintain.getCron(),
|
|
419
424
|
...getRoutingCrons(),
|
|
420
425
|
...blockletManager.getCrons(),
|
|
421
426
|
DiskMonitor.getCron(),
|
|
@@ -436,18 +441,15 @@ function ABTNode(options) {
|
|
|
436
441
|
|
|
437
442
|
const createCLILog = (action) => {
|
|
438
443
|
instance
|
|
439
|
-
.createAuditLog(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
},
|
|
449
|
-
instance
|
|
450
|
-
)
|
|
444
|
+
.createAuditLog({
|
|
445
|
+
action,
|
|
446
|
+
args: {},
|
|
447
|
+
context: formatContext({
|
|
448
|
+
user: { fullName: 'CLI', role: 'admin', did: options.nodeDid },
|
|
449
|
+
headers: { 'user-agent': 'CLI' },
|
|
450
|
+
}),
|
|
451
|
+
result: null,
|
|
452
|
+
})
|
|
451
453
|
.catch(console.error);
|
|
452
454
|
};
|
|
453
455
|
|
package/lib/router/helper.js
CHANGED
|
@@ -33,6 +33,7 @@ const {
|
|
|
33
33
|
WELLKNOWN_ACME_CHALLENGE_PREFIX,
|
|
34
34
|
WELLKNOWN_DID_RESOLVER_PREFIX,
|
|
35
35
|
WELLKNOWN_PING_PREFIX,
|
|
36
|
+
LOG_RETAIN_IN_DAYS,
|
|
36
37
|
} = require('@abtnode/constant');
|
|
37
38
|
const {
|
|
38
39
|
BLOCKLET_DYNAMIC_PATH_PREFIX,
|
|
@@ -1024,7 +1025,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1024
1025
|
const providerName = get(info, 'routing.provider', null);
|
|
1025
1026
|
|
|
1026
1027
|
if (providerName && providers[providerName] && typeof providers[providerName].rotateLogs === 'function') {
|
|
1027
|
-
await providers[providerName].rotateLogs();
|
|
1028
|
+
await providers[providerName].rotateLogs({ retain: LOG_RETAIN_IN_DAYS });
|
|
1028
1029
|
}
|
|
1029
1030
|
};
|
|
1030
1031
|
|
|
@@ -1250,7 +1251,7 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
1250
1251
|
},
|
|
1251
1252
|
{
|
|
1252
1253
|
name: 'rotate-log-files',
|
|
1253
|
-
time: '
|
|
1254
|
+
time: '1 0 0 * * *', // rotate at 00:00:01 every day
|
|
1254
1255
|
fn: rotateRouterLog,
|
|
1255
1256
|
options: { runOnInit: false },
|
|
1256
1257
|
},
|
package/lib/states/audit-log.js
CHANGED
|
@@ -208,6 +208,8 @@ const getLogContent = async (action, args, context, result, info, node) => {
|
|
|
208
208
|
return `updated basic server settings: \n${Object.keys(args).map(x => `- ${x}: ${args[x]}`).join('\n')}`; // prettier-ignore
|
|
209
209
|
case 'upgradeNodeVersion':
|
|
210
210
|
return `upgrade server to ${info.nextVersion}`;
|
|
211
|
+
case 'restartServer':
|
|
212
|
+
return 'server was restarted';
|
|
211
213
|
case 'startServer':
|
|
212
214
|
return 'server was started';
|
|
213
215
|
case 'stopServer':
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
/* eslint-disable no-underscore-dangle */
|
|
3
3
|
/* eslint-disable consistent-return */
|
|
4
4
|
const logger = require('@abtnode/logger')('state-blocklet-extras');
|
|
5
|
+
const { EXPIRED_BLOCKLET_DATA_RETENTION_DAYS } = require('@abtnode/constant');
|
|
5
6
|
const camelCase = require('lodash/camelCase');
|
|
6
7
|
const get = require('lodash/get');
|
|
8
|
+
const dayjs = require('dayjs');
|
|
7
9
|
|
|
8
10
|
const BaseState = require('./base');
|
|
9
11
|
|
|
10
12
|
const { mergeConfigs, parseConfigs } = require('../blocklet/extras');
|
|
13
|
+
const { validateAddMeta, validateExpiredInfo } = require('../validators/blocklet-extra');
|
|
11
14
|
|
|
12
15
|
const noop = (k) => (v) => v[k];
|
|
13
16
|
|
|
@@ -184,6 +187,33 @@ class BlockletExtrasState extends BaseState {
|
|
|
184
187
|
return list;
|
|
185
188
|
};
|
|
186
189
|
}
|
|
190
|
+
|
|
191
|
+
async addMeta({ did, meta, controller } = {}) {
|
|
192
|
+
const entity = { did, meta };
|
|
193
|
+
if (controller) {
|
|
194
|
+
entity.controller = controller;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
await validateAddMeta(entity);
|
|
198
|
+
|
|
199
|
+
return super.update({ did }, { $set: entity }, { upsert: true });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async updateExpireInfo({ did, expiredAt } = {}) {
|
|
203
|
+
const entity = { did, expiredAt };
|
|
204
|
+
await validateExpiredInfo(entity);
|
|
205
|
+
|
|
206
|
+
return super.update({ did }, { $set: { expiredAt } });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async getExpiredList() {
|
|
210
|
+
const now = dayjs();
|
|
211
|
+
|
|
212
|
+
return super.find({
|
|
213
|
+
'controller.nftId': { $exists: true },
|
|
214
|
+
expiredAt: { $exists: true, $lte: now.subtract(EXPIRED_BLOCKLET_DATA_RETENTION_DAYS, 'days').toISOString() },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
187
217
|
}
|
|
188
218
|
|
|
189
219
|
module.exports = BlockletExtrasState;
|
package/lib/states/node.js
CHANGED
|
@@ -179,7 +179,7 @@ class NodeState extends BaseState {
|
|
|
179
179
|
return nodeInfo;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
cleanupDirtyMaintainState() {
|
|
183
183
|
return this.read().then((doc) => {
|
|
184
184
|
if (doc.nextVersion && semver.lte(doc.nextVersion, doc.version)) {
|
|
185
185
|
const updates = { nextVersion: '', upgradeSessionId: '' };
|
package/lib/util/blocklet.js
CHANGED
|
@@ -11,6 +11,7 @@ const streamToPromise = require('stream-to-promise');
|
|
|
11
11
|
const { Throttle } = require('stream-throttle');
|
|
12
12
|
const ssri = require('ssri');
|
|
13
13
|
const diff = require('deep-diff');
|
|
14
|
+
const createArchive = require('archiver');
|
|
14
15
|
const axios = require('@abtnode/util/lib/axios');
|
|
15
16
|
const { stableStringify } = require('@arcblock/vc');
|
|
16
17
|
|
|
@@ -1492,6 +1493,26 @@ const consumeServerlessNFT = async ({ nftId, nodeInfo, blocklet }) => {
|
|
|
1492
1493
|
}
|
|
1493
1494
|
};
|
|
1494
1495
|
|
|
1496
|
+
const createDataArchive = (dataDir, fileName) => {
|
|
1497
|
+
const zipPath = path.join(os.tmpdir(), fileName);
|
|
1498
|
+
if (fs.existsSync(zipPath)) {
|
|
1499
|
+
fs.removeSync(zipPath);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const archive = createArchive('zip', { zlib: { level: 9 } });
|
|
1503
|
+
const stream = fs.createWriteStream(zipPath);
|
|
1504
|
+
|
|
1505
|
+
return new Promise((resolve, reject) => {
|
|
1506
|
+
archive
|
|
1507
|
+
.directory(dataDir, false)
|
|
1508
|
+
.on('error', (err) => reject(err))
|
|
1509
|
+
.pipe(stream);
|
|
1510
|
+
|
|
1511
|
+
stream.on('close', () => resolve(zipPath));
|
|
1512
|
+
archive.finalize();
|
|
1513
|
+
});
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1495
1516
|
module.exports = {
|
|
1496
1517
|
consumeServerlessNFT,
|
|
1497
1518
|
forEachBlocklet,
|
|
@@ -1534,4 +1555,5 @@ module.exports = {
|
|
|
1534
1555
|
getBlocklet,
|
|
1535
1556
|
ensureEnvDefault,
|
|
1536
1557
|
getConfigFromPreferences,
|
|
1558
|
+
createDataArchive,
|
|
1537
1559
|
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
const semver = require('semver');
|
|
3
|
+
const sleep = require('@abtnode/util/lib/sleep');
|
|
4
|
+
const SysInfo = require('systeminformation');
|
|
5
|
+
const { NODE_MODES, NODE_MAINTAIN_PROGRESS, EVENTS } = require('@abtnode/constant');
|
|
6
|
+
const listNpmPackageVersion = require('@abtnode/util/lib/list-npm-package-version');
|
|
7
|
+
const Lock = require('@abtnode/util/lib/lock');
|
|
8
|
+
const logger = require('@abtnode/logger')('@abtnode/core:maintain');
|
|
9
|
+
|
|
10
|
+
const states = require('../states');
|
|
11
|
+
const doRpc = require('./rpc');
|
|
12
|
+
|
|
13
|
+
const lock = new Lock('node-upgrade-lock');
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line no-unused-vars
|
|
16
|
+
const checkNewVersion = async (params, context) => {
|
|
17
|
+
try {
|
|
18
|
+
const info = await states.node.read();
|
|
19
|
+
|
|
20
|
+
if (!process.env.ABT_NODE_PACKAGE_NAME) {
|
|
21
|
+
logger.error('ABT_NODE_PACKAGE_NAME name was not found in environment');
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const versions = await listNpmPackageVersion(process.env.ABT_NODE_PACKAGE_NAME);
|
|
26
|
+
if (Array.isArray(versions) && versions.length) {
|
|
27
|
+
const latestVersion = versions[0].version;
|
|
28
|
+
if (semver.gt(latestVersion, info.version)) {
|
|
29
|
+
// if (semver.gte(latestVersion, info.version)) {
|
|
30
|
+
logger.info('New version found for Blocklet Server', {
|
|
31
|
+
latestVersion,
|
|
32
|
+
currentVersion: info.version,
|
|
33
|
+
nextVersion: info.nextVersion,
|
|
34
|
+
});
|
|
35
|
+
await states.node.updateNodeInfo({ nextVersion: latestVersion });
|
|
36
|
+
await states.notification.create({
|
|
37
|
+
title: 'Blocklet Server upgrade available',
|
|
38
|
+
description: 'A new and improved version of blocklet server is now available',
|
|
39
|
+
entityType: 'node',
|
|
40
|
+
severity: 'info',
|
|
41
|
+
sticky: true,
|
|
42
|
+
action: '/settings/about',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return latestVersion;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return '';
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.error('Failed to check new version for Blocklet Server', { error: err });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return '';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const triggerMaintain = async ({ action, next }) => {
|
|
58
|
+
if (['upgrade', 'restart'].includes(action) === false) {
|
|
59
|
+
throw new Error(`Unrecognized server maintain action: ${action}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const info = await states.node.read();
|
|
63
|
+
|
|
64
|
+
if (action === 'upgrade') {
|
|
65
|
+
if (!info.nextVersion) {
|
|
66
|
+
throw new Error('Do nothing since there is no next version to upgrade');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ensure we have enough memory for upgrading since the upgrading will consume a lot of memory
|
|
70
|
+
const { mem } = await SysInfo.get({ mem: '*' });
|
|
71
|
+
const freeMemory = mem.free / 1024 / 1024;
|
|
72
|
+
const availableMemory = mem.available / 1024 / 1024;
|
|
73
|
+
if (freeMemory < 200 && availableMemory < 300) {
|
|
74
|
+
throw new Error('Upgrade aborted because free memory not enough, please stop some blocklets and try again');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const from = info.version;
|
|
79
|
+
const to = action === 'upgrade' ? info.nextVersion : info.version;
|
|
80
|
+
|
|
81
|
+
// ensure we have an maintain session, we need a session to recover from crash
|
|
82
|
+
let sessionId = info.upgradeSessionId;
|
|
83
|
+
if (!sessionId) {
|
|
84
|
+
const result = await states.session.start({
|
|
85
|
+
action,
|
|
86
|
+
from,
|
|
87
|
+
to,
|
|
88
|
+
stage: NODE_MAINTAIN_PROGRESS.SETUP,
|
|
89
|
+
history: [],
|
|
90
|
+
startedAt: Date.now(),
|
|
91
|
+
});
|
|
92
|
+
sessionId = result.id;
|
|
93
|
+
logger.info(`generate new session for ${action}`, { from, to, sessionId });
|
|
94
|
+
await states.node.updateNodeInfo({ upgradeSessionId: sessionId });
|
|
95
|
+
}
|
|
96
|
+
const session = await states.session.read(sessionId);
|
|
97
|
+
if (!session) {
|
|
98
|
+
throw new Error(`${action} aborted due to invalid session: ${sessionId}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
process.nextTick(async () => {
|
|
102
|
+
await next(session);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return sessionId;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const resumeMaintain = async (session) => {
|
|
109
|
+
await lock.acquire();
|
|
110
|
+
|
|
111
|
+
const sessionId = session.id;
|
|
112
|
+
const { action, from, to } = session;
|
|
113
|
+
|
|
114
|
+
const goNextState = async (stage, previousSucceed) => {
|
|
115
|
+
session = await states.session.update(sessionId, {
|
|
116
|
+
stage,
|
|
117
|
+
history: [...session.history, { stage: session.stage, succeed: previousSucceed }],
|
|
118
|
+
});
|
|
119
|
+
// Emit events so client will keep up
|
|
120
|
+
states.node.emit(EVENTS.NODE_MAINTAIN_PROGRESS, session);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// 1. enter maintenance mode
|
|
124
|
+
if (session.stage === NODE_MAINTAIN_PROGRESS.SETUP) {
|
|
125
|
+
logger.info('enter maintenance mode', { from, to, sessionId });
|
|
126
|
+
await states.node.enterMode(NODE_MODES.MAINTENANCE);
|
|
127
|
+
if (action === 'upgrade') {
|
|
128
|
+
await goNextState(NODE_MAINTAIN_PROGRESS.INSTALLING, true);
|
|
129
|
+
}
|
|
130
|
+
if (action === 'restart') {
|
|
131
|
+
await goNextState(NODE_MAINTAIN_PROGRESS.VERIFYING, true);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 2. install new version
|
|
136
|
+
if (session.stage === NODE_MAINTAIN_PROGRESS.INSTALLING) {
|
|
137
|
+
const result = await doRpc({ command: 'install', version: to });
|
|
138
|
+
logger.info('new version installed', { from, to, sessionId });
|
|
139
|
+
if (result.code !== 0) {
|
|
140
|
+
await sleep(3000);
|
|
141
|
+
}
|
|
142
|
+
await goNextState(NODE_MAINTAIN_PROGRESS.VERIFYING, result.code === 0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 2. verify new version
|
|
146
|
+
if (session.stage === NODE_MAINTAIN_PROGRESS.VERIFYING) {
|
|
147
|
+
const result = await doRpc({ command: 'verify', version: to });
|
|
148
|
+
await sleep(3000);
|
|
149
|
+
if (result.code === 0) {
|
|
150
|
+
logger.info('new version verified', { from, to, sessionId });
|
|
151
|
+
await goNextState(NODE_MAINTAIN_PROGRESS.RESTARTING, true);
|
|
152
|
+
} else {
|
|
153
|
+
logger.info('new version verify failed', { from, to, sessionId });
|
|
154
|
+
await goNextState(NODE_MAINTAIN_PROGRESS.ROLLBACK, false);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 4. reset node.nextVersion/upgradeSessionId and exit maintenance mode
|
|
159
|
+
if (session.stage === NODE_MAINTAIN_PROGRESS.ROLLBACK) {
|
|
160
|
+
logger.info('rollback', { from, to, sessionId });
|
|
161
|
+
await sleep(3000);
|
|
162
|
+
await goNextState(NODE_MAINTAIN_PROGRESS.COMPLETE, true);
|
|
163
|
+
await sleep(5000);
|
|
164
|
+
await states.node.updateNodeInfo({ nextVersion: '', version: from, upgradeSessionId: '' });
|
|
165
|
+
try {
|
|
166
|
+
await states.node.exitMode(NODE_MODES.MAINTENANCE);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.error('Failed to rollback', { error });
|
|
169
|
+
}
|
|
170
|
+
await lock.release();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 3. restarting update version in node config and state
|
|
175
|
+
if (session.stage === NODE_MAINTAIN_PROGRESS.RESTARTING) {
|
|
176
|
+
logger.info('restart server', { from, to, sessionId });
|
|
177
|
+
await sleep(3000);
|
|
178
|
+
await goNextState(NODE_MAINTAIN_PROGRESS.CLEANUP, true);
|
|
179
|
+
await sleep(3000);
|
|
180
|
+
await doRpc({ command: 'restart', dataDir: process.env.ABT_NODE_DATA_DIR });
|
|
181
|
+
await lock.release();
|
|
182
|
+
return; // we should abort here and resume after restart
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 4. reset node.nextVersion/upgradeSessionId and exit maintenance mode
|
|
186
|
+
if (session.stage === NODE_MAINTAIN_PROGRESS.CLEANUP) {
|
|
187
|
+
logger.info('cleanup', { from, to, sessionId });
|
|
188
|
+
await goNextState(NODE_MAINTAIN_PROGRESS.COMPLETE, true);
|
|
189
|
+
await sleep(8000);
|
|
190
|
+
await states.node.updateNodeInfo({ nextVersion: '', version: to, upgradeSessionId: '' });
|
|
191
|
+
try {
|
|
192
|
+
await states.node.exitMode(NODE_MODES.MAINTENANCE);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
logger.error('Failed to exit maintenance mode', { error });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
await lock.release();
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const isBeingMaintained = async () => {
|
|
201
|
+
const info = await states.node.read();
|
|
202
|
+
if (!info.upgradeSessionId) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const session = await states.session.read(info.upgradeSessionId);
|
|
207
|
+
if (!session) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (session.action === 'upgrade' && !info.nextVersion) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return session;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const getCron = () => ({
|
|
219
|
+
name: 'check-update',
|
|
220
|
+
time: '0 0 8 * * *', // check every day
|
|
221
|
+
// time: '0 */5 * * * *', // check every 5 minutes
|
|
222
|
+
fn: async () => {
|
|
223
|
+
const info = await states.node.read();
|
|
224
|
+
if (!info.autoUpgrade) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
checkNewVersion();
|
|
229
|
+
},
|
|
230
|
+
options: { runOnInit: false },
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
module.exports = { getCron, checkNewVersion, resumeMaintain, triggerMaintain, isBeingMaintained };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const JOI = require('joi');
|
|
2
|
+
const { didExtension } = require('@blocklet/meta/lib/extension');
|
|
3
|
+
const { blockletController, createValidator } = require('./util');
|
|
4
|
+
|
|
5
|
+
const Joi = JOI.extend(didExtension);
|
|
6
|
+
|
|
7
|
+
const addMeta = Joi.object({
|
|
8
|
+
did: Joi.ref('meta.did'),
|
|
9
|
+
meta: Joi.object({
|
|
10
|
+
did: Joi.DID().required(),
|
|
11
|
+
name: Joi.string().required(),
|
|
12
|
+
}).required(),
|
|
13
|
+
controller: blockletController.optional(),
|
|
14
|
+
}).required();
|
|
15
|
+
|
|
16
|
+
const updateExpiredInfo = Joi.object({
|
|
17
|
+
did: Joi.DID().required(),
|
|
18
|
+
expiredAt: Joi.string().required(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
validateAddMeta: createValidator(addMeta),
|
|
23
|
+
validateExpiredInfo: createValidator(updateExpiredInfo),
|
|
24
|
+
};
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
const { didExtension } = require('@blocklet/meta/lib/extension');
|
|
3
|
-
|
|
4
|
-
const Joi = JOI.extend(didExtension);
|
|
5
|
-
|
|
6
|
-
const blockletController = Joi.object({
|
|
7
|
-
nftId: Joi.DID().required(),
|
|
8
|
-
nftOwner: Joi.DID().required(),
|
|
9
|
-
appMaxCount: Joi.number().required().min(1),
|
|
10
|
-
}).options({ stripUnknown: true });
|
|
1
|
+
const { blockletController } = require('./util');
|
|
11
2
|
|
|
12
3
|
module.exports = {
|
|
13
4
|
validateBlockletController: (entity) => blockletController.validateAsync(entity),
|
package/lib/validators/util.js
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
const get = require('lodash/get');
|
|
2
|
+
const JOI = require('joi');
|
|
3
|
+
const { didExtension } = require('@blocklet/meta/lib/extension');
|
|
4
|
+
|
|
5
|
+
const Joi = JOI.extend(didExtension);
|
|
6
|
+
|
|
7
|
+
const getMultipleLangParams = (context) => ({
|
|
8
|
+
errors: {
|
|
9
|
+
language: get(context, 'query.locale', 'en'),
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const blockletController = Joi.object({
|
|
14
|
+
nftId: Joi.DID().required(),
|
|
15
|
+
nftOwner: Joi.DID().required(),
|
|
16
|
+
appMaxCount: Joi.number().required().min(1),
|
|
17
|
+
}).options({ stripUnknown: true });
|
|
18
|
+
|
|
19
|
+
const createValidator = (schema) => (entity) => schema.validateAsync(entity);
|
|
2
20
|
|
|
3
21
|
module.exports = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
},
|
|
8
|
-
}),
|
|
22
|
+
createValidator,
|
|
23
|
+
getMultipleLangParams,
|
|
24
|
+
blockletController,
|
|
9
25
|
};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.8.
|
|
6
|
+
"version": "1.8.57",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,18 +19,18 @@
|
|
|
19
19
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@abtnode/auth": "1.8.
|
|
23
|
-
"@abtnode/certificate-manager": "1.8.
|
|
24
|
-
"@abtnode/constant": "1.8.
|
|
25
|
-
"@abtnode/cron": "1.8.
|
|
26
|
-
"@abtnode/db": "1.8.
|
|
27
|
-
"@abtnode/logger": "1.8.
|
|
28
|
-
"@abtnode/queue": "1.8.
|
|
29
|
-
"@abtnode/rbac": "1.8.
|
|
30
|
-
"@abtnode/router-provider": "1.8.
|
|
31
|
-
"@abtnode/static-server": "1.8.
|
|
32
|
-
"@abtnode/timemachine": "1.8.
|
|
33
|
-
"@abtnode/util": "1.8.
|
|
22
|
+
"@abtnode/auth": "1.8.57",
|
|
23
|
+
"@abtnode/certificate-manager": "1.8.57",
|
|
24
|
+
"@abtnode/constant": "1.8.57",
|
|
25
|
+
"@abtnode/cron": "1.8.57",
|
|
26
|
+
"@abtnode/db": "1.8.57",
|
|
27
|
+
"@abtnode/logger": "1.8.57",
|
|
28
|
+
"@abtnode/queue": "1.8.57",
|
|
29
|
+
"@abtnode/rbac": "1.8.57",
|
|
30
|
+
"@abtnode/router-provider": "1.8.57",
|
|
31
|
+
"@abtnode/static-server": "1.8.57",
|
|
32
|
+
"@abtnode/timemachine": "1.8.57",
|
|
33
|
+
"@abtnode/util": "1.8.57",
|
|
34
34
|
"@arcblock/did": "1.18.34",
|
|
35
35
|
"@arcblock/did-motif": "^1.1.10",
|
|
36
36
|
"@arcblock/did-util": "1.18.34",
|
|
@@ -38,17 +38,19 @@
|
|
|
38
38
|
"@arcblock/jwt": "^1.18.34",
|
|
39
39
|
"@arcblock/pm2-events": "^0.0.5",
|
|
40
40
|
"@arcblock/vc": "1.18.34",
|
|
41
|
-
"@blocklet/constant": "1.8.
|
|
42
|
-
"@blocklet/meta": "1.8.
|
|
43
|
-
"@blocklet/sdk": "1.8.
|
|
41
|
+
"@blocklet/constant": "1.8.57",
|
|
42
|
+
"@blocklet/meta": "1.8.57",
|
|
43
|
+
"@blocklet/sdk": "1.8.57",
|
|
44
44
|
"@fidm/x509": "^1.2.1",
|
|
45
45
|
"@ocap/mcrypto": "1.18.34",
|
|
46
46
|
"@ocap/util": "1.18.34",
|
|
47
47
|
"@ocap/wallet": "1.18.34",
|
|
48
48
|
"@slack/webhook": "^5.0.4",
|
|
49
|
+
"archiver": "^5.3.1",
|
|
49
50
|
"axios": "^0.27.2",
|
|
50
51
|
"axon": "^2.0.3",
|
|
51
52
|
"chalk": "^4.1.2",
|
|
53
|
+
"dayjs": "^1.11.7",
|
|
52
54
|
"deep-diff": "^1.0.2",
|
|
53
55
|
"detect-port": "^1.5.1",
|
|
54
56
|
"escape-string-regexp": "^4.0.0",
|
|
@@ -83,5 +85,5 @@
|
|
|
83
85
|
"express": "^4.18.2",
|
|
84
86
|
"jest": "^27.5.1"
|
|
85
87
|
},
|
|
86
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "2573eb9370c79853cba88cba418a964fb1cc8949"
|
|
87
89
|
}
|
package/lib/util/upgrade.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-param-reassign */
|
|
2
|
-
const semver = require('semver');
|
|
3
|
-
const sleep = require('@abtnode/util/lib/sleep');
|
|
4
|
-
const { NODE_MODES, NODE_UPGRADE_PROGRESS, EVENTS } = require('@abtnode/constant');
|
|
5
|
-
const listNpmPackageVersion = require('@abtnode/util/lib/list-npm-package-version');
|
|
6
|
-
const Lock = require('@abtnode/util/lib/lock');
|
|
7
|
-
const logger = require('@abtnode/logger')('@abtnode/core:upgrade');
|
|
8
|
-
|
|
9
|
-
const states = require('../states');
|
|
10
|
-
const doRpc = require('./rpc');
|
|
11
|
-
|
|
12
|
-
const lock = new Lock('node-upgrade-lock');
|
|
13
|
-
|
|
14
|
-
// eslint-disable-next-line no-unused-vars
|
|
15
|
-
const checkNewVersion = async (params, context) => {
|
|
16
|
-
try {
|
|
17
|
-
const info = await states.node.read();
|
|
18
|
-
|
|
19
|
-
if (!process.env.ABT_NODE_PACKAGE_NAME) {
|
|
20
|
-
logger.error('ABT_NODE_PACKAGE_NAME name was not found in environment');
|
|
21
|
-
return '';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const versions = await listNpmPackageVersion(process.env.ABT_NODE_PACKAGE_NAME);
|
|
25
|
-
if (Array.isArray(versions) && versions.length) {
|
|
26
|
-
const latestVersion = versions[0].version;
|
|
27
|
-
if (semver.gt(latestVersion, info.version)) {
|
|
28
|
-
// if (semver.gte(latestVersion, info.version)) {
|
|
29
|
-
logger.info('New version found for Blocklet Server', {
|
|
30
|
-
latestVersion,
|
|
31
|
-
currentVersion: info.version,
|
|
32
|
-
nextVersion: info.nextVersion,
|
|
33
|
-
});
|
|
34
|
-
await states.node.updateNodeInfo({ nextVersion: latestVersion });
|
|
35
|
-
await states.notification.create({
|
|
36
|
-
title: 'Blocklet Server upgrade available',
|
|
37
|
-
description: 'A new and improved version of blocklet server is now available',
|
|
38
|
-
entityType: 'node',
|
|
39
|
-
severity: 'info',
|
|
40
|
-
sticky: true,
|
|
41
|
-
action: '/settings/about',
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return latestVersion;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return '';
|
|
49
|
-
} catch (err) {
|
|
50
|
-
logger.error('Failed to check new version for Blocklet Server', { error: err });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return '';
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// eslint-disable-next-line no-unused-vars
|
|
57
|
-
const startUpgrade = async (params, context) => {
|
|
58
|
-
const info = await states.node.read();
|
|
59
|
-
|
|
60
|
-
if (!info.nextVersion) {
|
|
61
|
-
throw new Error('Do nothing since there is no next version to upgrade');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const from = info.version;
|
|
65
|
-
const to = info.nextVersion;
|
|
66
|
-
|
|
67
|
-
// 0. ensure we have an upgrading session, we need a session to recover from crash
|
|
68
|
-
let sessionId = info.upgradeSessionId;
|
|
69
|
-
if (!sessionId) {
|
|
70
|
-
const result = await states.session.start({ from, to, stage: NODE_UPGRADE_PROGRESS.SETUP, startedAt: Date.now() });
|
|
71
|
-
sessionId = result.id;
|
|
72
|
-
logger.info('generate new session for upgrade', { from, to, sessionId });
|
|
73
|
-
await states.node.updateNodeInfo({ upgradeSessionId: sessionId });
|
|
74
|
-
}
|
|
75
|
-
const session = await states.session.read(sessionId);
|
|
76
|
-
if (!session) {
|
|
77
|
-
throw new Error(`Upgrade aborted due to invalid session: ${sessionId}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
process.nextTick(async () => {
|
|
81
|
-
await doUpgrade(session);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
return sessionId;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// TODO: error handling and rollback support
|
|
88
|
-
const doUpgrade = async (session) => {
|
|
89
|
-
await lock.acquire();
|
|
90
|
-
|
|
91
|
-
const sessionId = session.id;
|
|
92
|
-
const info = await states.node.read();
|
|
93
|
-
const from = info.version;
|
|
94
|
-
const to = info.nextVersion;
|
|
95
|
-
|
|
96
|
-
const goNextState = async (stage) => {
|
|
97
|
-
session = await states.session.update(sessionId, { stage });
|
|
98
|
-
// Emit events so client will keep up
|
|
99
|
-
states.node.emit(EVENTS.NODE_UPGRADE_PROGRESS, session);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// 1. enter maintenance mode
|
|
103
|
-
if (session.stage === NODE_UPGRADE_PROGRESS.SETUP) {
|
|
104
|
-
logger.info('enter maintenance mode for upgrading', { from, to, sessionId });
|
|
105
|
-
await states.node.enterMode(NODE_MODES.MAINTENANCE);
|
|
106
|
-
await goNextState(NODE_UPGRADE_PROGRESS.INSTALLING);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 2. install specified version
|
|
110
|
-
if (session.stage === NODE_UPGRADE_PROGRESS.INSTALLING) {
|
|
111
|
-
await doRpc({ command: 'install', version: to });
|
|
112
|
-
logger.info('new version installed for upgrading', { from, to, sessionId });
|
|
113
|
-
await goNextState(NODE_UPGRADE_PROGRESS.RESTARTING);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// 3. restarting update version in node config and state
|
|
117
|
-
if (session.stage === NODE_UPGRADE_PROGRESS.RESTARTING) {
|
|
118
|
-
logger.info('daemon restarted for upgrading', { from, to, sessionId });
|
|
119
|
-
await sleep(3000);
|
|
120
|
-
await goNextState(NODE_UPGRADE_PROGRESS.CLEANUP);
|
|
121
|
-
await sleep(3000);
|
|
122
|
-
await doRpc({ command: 'restart', dataDir: process.env.ABT_NODE_DATA_DIR });
|
|
123
|
-
await lock.release();
|
|
124
|
-
return; // we should abort here and resume after restart
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 4. reset node.nextVersion/upgradeSessionId and exit maintenance mode
|
|
128
|
-
if (session.stage === NODE_UPGRADE_PROGRESS.CLEANUP) {
|
|
129
|
-
logger.info('cleanup for upgrading', { from, to, sessionId });
|
|
130
|
-
await goNextState(NODE_UPGRADE_PROGRESS.COMPLETE);
|
|
131
|
-
await sleep(8000);
|
|
132
|
-
await states.node.updateNodeInfo({ nextVersion: '', version: to, upgradeSessionId: '' });
|
|
133
|
-
try {
|
|
134
|
-
await states.node.exitMode(NODE_MODES.MAINTENANCE);
|
|
135
|
-
} catch (error) {
|
|
136
|
-
logger.error('Failed to exit maintenance mode', { error });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
await lock.release();
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const isUpgrading = async () => {
|
|
143
|
-
const info = await states.node.read();
|
|
144
|
-
if (!info.nextVersion) {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!info.upgradeSessionId) {
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const session = await states.session.read(info.upgradeSessionId);
|
|
153
|
-
if (!session) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return session;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const getCron = () => ({
|
|
161
|
-
name: 'check-update',
|
|
162
|
-
time: '0 0 8 * * *', // check every day
|
|
163
|
-
// time: '0 */5 * * * *', // check every 5 minutes
|
|
164
|
-
fn: async () => {
|
|
165
|
-
const info = await states.node.read();
|
|
166
|
-
if (!info.autoUpgrade) {
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
checkNewVersion();
|
|
171
|
-
},
|
|
172
|
-
options: { runOnInit: false },
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
module.exports = { getCron, checkNewVersion, doUpgrade, startUpgrade, isUpgrading };
|