@abtnode/core 1.17.3-beta-20251120-052956-035abea6 → 1.17.3-beta-20251121-135300-8e451e6e
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 +148 -222
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +229 -114
- package/lib/blocklet/manager/helper/blue-green-upgrade-blocklet.js +10 -0
- package/lib/states/blocklet.js +12 -10
- package/lib/util/blocklet.js +47 -4
- package/lib/util/docker/docker-exec-chown.js +3 -1
- package/lib/util/docker/docker-exec.js +4 -2
- package/package.json +24 -24
|
@@ -259,13 +259,13 @@ const { formatEnvironments, getBlockletMeta, validateOwner, isCLI } = util;
|
|
|
259
259
|
|
|
260
260
|
const statusLock = new DBCache(() => ({
|
|
261
261
|
prefix: 'blocklet-status-lock',
|
|
262
|
-
ttl:
|
|
262
|
+
ttl: 120_000,
|
|
263
263
|
...getAbtNodeRedisAndSQLiteUrl(),
|
|
264
264
|
}));
|
|
265
265
|
|
|
266
266
|
const startLock = new DBCache(() => ({
|
|
267
267
|
prefix: 'blocklet-start-lock',
|
|
268
|
-
ttl: process.env.NODE_ENV === 'test' ?
|
|
268
|
+
ttl: process.env.NODE_ENV === 'test' ? 1_000 : 120_000,
|
|
269
269
|
...getAbtNodeRedisAndSQLiteUrl(),
|
|
270
270
|
}));
|
|
271
271
|
|
|
@@ -740,6 +740,8 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
740
740
|
e2eMode,
|
|
741
741
|
context,
|
|
742
742
|
atomic,
|
|
743
|
+
onStarted,
|
|
744
|
+
onError,
|
|
743
745
|
}) {
|
|
744
746
|
if (!blocklet.children) {
|
|
745
747
|
return componentDids;
|
|
@@ -771,11 +773,17 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
771
773
|
// start by dependency
|
|
772
774
|
for (const dids of requiredDidsLayers) {
|
|
773
775
|
if (atomic) {
|
|
774
|
-
await this._start(
|
|
776
|
+
await this._start(
|
|
777
|
+
{ blocklet, throwOnError, checkHealthImmediately, e2eMode, componentDids: dids, onStarted, onError },
|
|
778
|
+
context
|
|
779
|
+
);
|
|
775
780
|
} else {
|
|
776
781
|
const tasks = dids.map(
|
|
777
782
|
(x) => () =>
|
|
778
|
-
this._start(
|
|
783
|
+
this._start(
|
|
784
|
+
{ blocklet, throwOnError, checkHealthImmediately, e2eMode, componentDids: [x], onStarted, onError },
|
|
785
|
+
context
|
|
786
|
+
)
|
|
779
787
|
);
|
|
780
788
|
await pAll(tasks, { concurrency: 4 }).catch((err) => {
|
|
781
789
|
throw new Error(err.errors ? err.errors.join(', ') : err);
|
|
@@ -801,8 +809,8 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
801
809
|
},
|
|
802
810
|
context
|
|
803
811
|
) {
|
|
804
|
-
const lockName =
|
|
805
|
-
const shouldLock =
|
|
812
|
+
const lockName = `${did}-start`;
|
|
813
|
+
const shouldLock = inputComponentDids?.length === 0;
|
|
806
814
|
|
|
807
815
|
if (shouldLock) {
|
|
808
816
|
await startLock.acquire(lockName);
|
|
@@ -814,8 +822,16 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
814
822
|
inputComponentDids?.length ? inputComponentDids : blocklet.children.map((x) => x.meta.did)
|
|
815
823
|
);
|
|
816
824
|
|
|
825
|
+
try {
|
|
826
|
+
await this.deleteProcess({ did, componentDids: baseComponentDids, isGreen: true });
|
|
827
|
+
} catch (error) {
|
|
828
|
+
logger.warn('Failed to delete process; this warning can be safely ignored.', { error });
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
await this.checkControllerStatus(blocklet, 'start');
|
|
832
|
+
|
|
817
833
|
// 优先启动资源型组件
|
|
818
|
-
|
|
834
|
+
const componentDids = [];
|
|
819
835
|
const nonEntryComponentIds = [];
|
|
820
836
|
for (const b of blocklet.children) {
|
|
821
837
|
if (!b?.meta?.did) {
|
|
@@ -844,7 +860,6 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
844
860
|
operator,
|
|
845
861
|
});
|
|
846
862
|
}
|
|
847
|
-
|
|
848
863
|
// sync component config before at first to ensure resource component config is ready
|
|
849
864
|
const serverSk = (await states.node.read()).sk;
|
|
850
865
|
await Promise.all(
|
|
@@ -853,7 +868,16 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
853
868
|
)
|
|
854
869
|
);
|
|
855
870
|
|
|
856
|
-
|
|
871
|
+
const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
|
|
872
|
+
componentDids,
|
|
873
|
+
operator,
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
this.emit(BlockletEvents.statusChange, doc1);
|
|
877
|
+
const startedBlockletDids = [];
|
|
878
|
+
const errorBlockletDids = [];
|
|
879
|
+
|
|
880
|
+
const notStartedComponentDids = await this.startRequiredComponents({
|
|
857
881
|
componentDids,
|
|
858
882
|
inputComponentDids,
|
|
859
883
|
blocklet,
|
|
@@ -862,26 +886,15 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
862
886
|
e2eMode,
|
|
863
887
|
context,
|
|
864
888
|
atomic,
|
|
889
|
+
onStarted: (subDid) => {
|
|
890
|
+
startedBlockletDids.push({ did: subDid });
|
|
891
|
+
},
|
|
892
|
+
onError: (subDid, error) => {
|
|
893
|
+
errorBlockletDids.push({ did: subDid, error });
|
|
894
|
+
},
|
|
865
895
|
});
|
|
866
896
|
|
|
867
|
-
|
|
868
|
-
const nextBlocklet = await this._start(
|
|
869
|
-
{ blocklet, throwOnError, checkHealthImmediately, e2eMode, componentDids, operator },
|
|
870
|
-
context
|
|
871
|
-
);
|
|
872
|
-
|
|
873
|
-
// 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
|
|
874
|
-
if (!['true', '1'].includes(process.env.ABT_NODE_DISABLE_BLUE_GREEN)) {
|
|
875
|
-
this.emit(BlockletEvents.blurOrGreenStarted, {
|
|
876
|
-
blocklet: nextBlocklet,
|
|
877
|
-
componentDids: inputComponentDids,
|
|
878
|
-
context,
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
return nextBlocklet;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
const tasks = componentDids.map(
|
|
897
|
+
const tasks = notStartedComponentDids.map(
|
|
885
898
|
(componentDid) => () =>
|
|
886
899
|
this._start(
|
|
887
900
|
{
|
|
@@ -891,25 +904,104 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
891
904
|
e2eMode,
|
|
892
905
|
componentDids: [componentDid],
|
|
893
906
|
operator,
|
|
907
|
+
onStarted: (subDid) => {
|
|
908
|
+
startedBlockletDids.push({ did: subDid });
|
|
909
|
+
},
|
|
910
|
+
onError: (subDid, error) => {
|
|
911
|
+
errorBlockletDids.push({ did: subDid, error });
|
|
912
|
+
},
|
|
894
913
|
},
|
|
895
914
|
context
|
|
896
915
|
)
|
|
897
916
|
);
|
|
898
917
|
|
|
899
|
-
|
|
918
|
+
await pAll(tasks, { concurrency: 6 });
|
|
900
919
|
|
|
901
920
|
const nextBlocklet = await this.ensureBlocklet(did, { e2eMode });
|
|
921
|
+
let errorDescription = '';
|
|
922
|
+
let resultBlocklet = nextBlocklet;
|
|
923
|
+
|
|
924
|
+
if (startedBlockletDids.length) {
|
|
925
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
926
|
+
componentDids: startedBlockletDids.map((x) => x.did),
|
|
927
|
+
operator,
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
const finalBlocklet = await this.getBlocklet(did);
|
|
931
|
+
resultBlocklet = finalBlocklet;
|
|
932
|
+
|
|
933
|
+
await this.configSynchronizer.throttledSyncAppConfig(finalBlocklet);
|
|
934
|
+
const componentsInfo = getComponentsInternalInfo(finalBlocklet);
|
|
935
|
+
this.emit(BlockletInternalEvents.componentUpdated, {
|
|
936
|
+
appDid: blocklet.appDid,
|
|
937
|
+
components: componentsInfo,
|
|
938
|
+
});
|
|
939
|
+
this.emit(BlockletInternalEvents.componentStarted, {
|
|
940
|
+
appDid: blocklet.appDid,
|
|
941
|
+
components: (componentDids || []).map((x) => ({ did: x })),
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
this.emit(BlockletEvents.statusChange, finalBlocklet);
|
|
945
|
+
this.emit(BlockletEvents.started, { ...finalBlocklet, componentDids });
|
|
946
|
+
|
|
947
|
+
launcher.notifyBlockletStarted(finalBlocklet);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (errorBlockletDids.length) {
|
|
951
|
+
const error = errorBlockletDids[0];
|
|
952
|
+
for (const item of errorBlockletDids) {
|
|
953
|
+
logger.error('Failed to start blocklet', {
|
|
954
|
+
error: item.error,
|
|
955
|
+
title: nextBlocklet.meta.title,
|
|
956
|
+
name: getComponentNamesWithVersion(nextBlocklet, [item.did]),
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
errorDescription = `${getComponentNamesWithVersion(
|
|
961
|
+
nextBlocklet,
|
|
962
|
+
errorBlockletDids.map((x) => x.did)
|
|
963
|
+
)} start failed for ${getDisplayName(nextBlocklet)}: ${errorBlockletDids.map((x) => x.error.message).join('. ')}`;
|
|
964
|
+
this._createNotification(did, {
|
|
965
|
+
title: 'Component start failed',
|
|
966
|
+
description: errorDescription,
|
|
967
|
+
entityType: 'blocklet',
|
|
968
|
+
entityId: did,
|
|
969
|
+
severity: 'error',
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
await this.deleteProcess({
|
|
973
|
+
did,
|
|
974
|
+
componentDids: errorBlockletDids.map((x) => x.did),
|
|
975
|
+
shouldUpdateBlockletStatus: false,
|
|
976
|
+
});
|
|
977
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
978
|
+
componentDids: errorBlockletDids.map((x) => x.did),
|
|
979
|
+
operator,
|
|
980
|
+
});
|
|
981
|
+
const finalBlocklet = await this.getBlocklet(did);
|
|
982
|
+
resultBlocklet = finalBlocklet;
|
|
983
|
+
this.emit(BlockletEvents.startFailed, {
|
|
984
|
+
...finalBlocklet,
|
|
985
|
+
componentDids: errorBlockletDids.map((x) => x.did),
|
|
986
|
+
error: { message: error.message },
|
|
987
|
+
});
|
|
988
|
+
this.emit(BlockletEvents.statusChange, { ...finalBlocklet, error: { message: error.message } });
|
|
989
|
+
}
|
|
902
990
|
|
|
903
991
|
// 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
|
|
904
992
|
if (!['true', '1'].includes(process.env.ABT_NODE_DISABLE_BLUE_GREEN)) {
|
|
905
993
|
this.emit(BlockletEvents.blurOrGreenStarted, {
|
|
906
|
-
blocklet:
|
|
994
|
+
blocklet: resultBlocklet,
|
|
907
995
|
componentDids: inputComponentDids,
|
|
908
996
|
context,
|
|
909
997
|
});
|
|
910
998
|
}
|
|
911
999
|
|
|
912
|
-
|
|
1000
|
+
if (throwOnError && errorDescription) {
|
|
1001
|
+
throw new Error(errorDescription);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
return resultBlocklet;
|
|
913
1005
|
} finally {
|
|
914
1006
|
if (shouldLock) {
|
|
915
1007
|
await startLock.releaseLock(lockName);
|
|
@@ -926,17 +1018,13 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
926
1018
|
e2eMode = false,
|
|
927
1019
|
componentDids,
|
|
928
1020
|
operator: _operator,
|
|
1021
|
+
onStarted,
|
|
1022
|
+
onError,
|
|
929
1023
|
},
|
|
930
1024
|
context
|
|
931
1025
|
) {
|
|
932
1026
|
const operator = _operator || context?.user?.did;
|
|
933
1027
|
|
|
934
|
-
try {
|
|
935
|
-
await this.deleteProcess({ did, componentDids, isGreen: true });
|
|
936
|
-
} catch {
|
|
937
|
-
// do nothing
|
|
938
|
-
}
|
|
939
|
-
|
|
940
1028
|
logger.info('start blocklet', {
|
|
941
1029
|
did: did || inputBlocklet.meta.did,
|
|
942
1030
|
componentDids,
|
|
@@ -960,7 +1048,6 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
960
1048
|
|
|
961
1049
|
const entryComponentIds = [];
|
|
962
1050
|
const nonEntryComponentIds = [];
|
|
963
|
-
let nonEntryComponentRes;
|
|
964
1051
|
const componentDidsSet = new Set(componentDids);
|
|
965
1052
|
try {
|
|
966
1053
|
await forEachBlocklet(
|
|
@@ -996,18 +1083,9 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
996
1083
|
{ parallel: true, concurrencyLimit: 4 }
|
|
997
1084
|
);
|
|
998
1085
|
if (nonEntryComponentIds.length) {
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
minConsecutiveTime: 3000,
|
|
1003
|
-
timeout: 5000,
|
|
1004
|
-
componentDids: nonEntryComponentIds,
|
|
1005
|
-
};
|
|
1006
|
-
nonEntryComponentRes = await this._onCheckIfStarted(params, {
|
|
1007
|
-
throwOnError,
|
|
1008
|
-
skipRunningCheck: true,
|
|
1009
|
-
needUpdateBlueStatus: false,
|
|
1010
|
-
});
|
|
1086
|
+
for (const subDid of nonEntryComponentIds) {
|
|
1087
|
+
onStarted?.(subDid);
|
|
1088
|
+
}
|
|
1011
1089
|
}
|
|
1012
1090
|
} catch (err) {
|
|
1013
1091
|
logger.error('Failed to categorize components into entry and non-entry types', { error: err.message });
|
|
@@ -1015,7 +1093,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
1015
1093
|
}
|
|
1016
1094
|
|
|
1017
1095
|
if (!entryComponentIds.length) {
|
|
1018
|
-
return
|
|
1096
|
+
return;
|
|
1019
1097
|
}
|
|
1020
1098
|
|
|
1021
1099
|
try {
|
|
@@ -1036,13 +1114,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
1036
1114
|
}
|
|
1037
1115
|
}
|
|
1038
1116
|
|
|
1039
|
-
const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
|
|
1040
|
-
componentDids: entryComponentIds,
|
|
1041
|
-
operator,
|
|
1042
|
-
});
|
|
1043
|
-
|
|
1044
1117
|
blocklet1.status = BlockletStatus.starting;
|
|
1045
|
-
this.emit(BlockletEvents.statusChange, doc1);
|
|
1046
1118
|
const blocklet = await ensureAppPortsNotOccupied({
|
|
1047
1119
|
blocklet: blocklet1,
|
|
1048
1120
|
componentDids: entryComponentIds,
|
|
@@ -1097,8 +1169,6 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
1097
1169
|
});
|
|
1098
1170
|
};
|
|
1099
1171
|
|
|
1100
|
-
await this.configSynchronizer.syncAppConfig(blocklet);
|
|
1101
|
-
|
|
1102
1172
|
await startBlockletProcess(blocklet, {
|
|
1103
1173
|
...context,
|
|
1104
1174
|
preFlight: getHookFn('preFlight'),
|
|
@@ -1116,6 +1186,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
1116
1186
|
checkHealthImmediately,
|
|
1117
1187
|
componentDids,
|
|
1118
1188
|
});
|
|
1189
|
+
|
|
1119
1190
|
const params = {
|
|
1120
1191
|
did,
|
|
1121
1192
|
context,
|
|
@@ -1124,47 +1195,19 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
1124
1195
|
componentDids: entryComponentIds,
|
|
1125
1196
|
};
|
|
1126
1197
|
|
|
1127
|
-
await this._onCheckIfStarted(params, { throwOnError
|
|
1128
|
-
blocklet.status = BlockletStatus.running;
|
|
1198
|
+
await this._onCheckIfStarted(params, { throwOnError });
|
|
1129
1199
|
|
|
1130
|
-
|
|
1200
|
+
for (const subDid of entryComponentIds) {
|
|
1201
|
+
onStarted?.(subDid);
|
|
1202
|
+
}
|
|
1131
1203
|
} catch (err) {
|
|
1132
1204
|
const status = await states.blocklet.getBlockletStatus(did);
|
|
1133
1205
|
if ([BlockletStatus.stopping, BlockletStatus.stopped].includes(status)) {
|
|
1134
1206
|
logger.info('Failed to start blocklet maybe due to manually stopped');
|
|
1135
|
-
return states.blocklet.getBlocklet(did);
|
|
1136
1207
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
logger.error('Failed to start blocklet', { error, did, title: blocklet1.meta.title });
|
|
1140
|
-
const description = `${getComponentNamesWithVersion(blocklet1, entryComponentIds)} start failed for ${getDisplayName(
|
|
1141
|
-
blocklet1
|
|
1142
|
-
)}: ${error.message}`;
|
|
1143
|
-
this._createNotification(did, {
|
|
1144
|
-
title: 'Component start failed',
|
|
1145
|
-
description,
|
|
1146
|
-
entityType: 'blocklet',
|
|
1147
|
-
entityId: did,
|
|
1148
|
-
severity: 'error',
|
|
1149
|
-
});
|
|
1150
|
-
|
|
1151
|
-
await this.deleteProcess({ did, componentDids: entryComponentIds });
|
|
1152
|
-
const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
1153
|
-
componentDids: entryComponentIds,
|
|
1154
|
-
operator,
|
|
1155
|
-
});
|
|
1156
|
-
this.emit(BlockletEvents.startFailed, {
|
|
1157
|
-
...res,
|
|
1158
|
-
componentDids: entryComponentIds,
|
|
1159
|
-
error: { message: error.message },
|
|
1160
|
-
});
|
|
1161
|
-
this.emit(BlockletEvents.statusChange, { ...res, error: { message: error.message } });
|
|
1162
|
-
|
|
1163
|
-
if (throwOnError) {
|
|
1164
|
-
throw new Error(description);
|
|
1208
|
+
for (const subDid of entryComponentIds) {
|
|
1209
|
+
onError?.(subDid, err);
|
|
1165
1210
|
}
|
|
1166
|
-
|
|
1167
|
-
return res;
|
|
1168
1211
|
}
|
|
1169
1212
|
}
|
|
1170
1213
|
|
|
@@ -3590,141 +3633,24 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
3590
3633
|
await blueGreenStartBlocklet({ did, componentDids, operator }, context, this, states);
|
|
3591
3634
|
}
|
|
3592
3635
|
|
|
3593
|
-
_onCheckIfStarted = async (
|
|
3594
|
-
jobInfo
|
|
3595
|
-
{ throwOnError, skipRunningCheck = false, isGreen = false, needUpdateBlueStatus = true } = {}
|
|
3596
|
-
) => {
|
|
3597
|
-
const startedAt = Date.now();
|
|
3598
|
-
const { did, context, minConsecutiveTime = 2000, timeout, componentDids } = jobInfo;
|
|
3599
|
-
const nodeInfo = await states.node.read();
|
|
3636
|
+
_onCheckIfStarted = async (jobInfo, { skipRunningCheck = false, isGreen = false } = {}) => {
|
|
3637
|
+
const { did, minConsecutiveTime = 2000, timeout, componentDids } = jobInfo;
|
|
3600
3638
|
const blocklet = await this.getBlocklet(did);
|
|
3601
3639
|
|
|
3602
|
-
const
|
|
3603
|
-
const { name } = meta;
|
|
3604
|
-
|
|
3605
|
-
const successBlockletIds = new Set();
|
|
3606
|
-
|
|
3607
|
-
try {
|
|
3608
|
-
if (!skipRunningCheck) {
|
|
3609
|
-
await checkBlockletProcessHealthy(blocklet, {
|
|
3610
|
-
minConsecutiveTime,
|
|
3611
|
-
timeout,
|
|
3612
|
-
componentDids,
|
|
3613
|
-
isGreen,
|
|
3614
|
-
appDid: did,
|
|
3615
|
-
enableDocker: nodeInfo.enableDocker,
|
|
3616
|
-
getBlocklet: () => {
|
|
3617
|
-
return this.getBlocklet(did);
|
|
3618
|
-
},
|
|
3619
|
-
setBlockletRunning: async (componentDid) => {
|
|
3620
|
-
successBlockletIds.add(componentDid);
|
|
3621
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
3622
|
-
componentDids: [componentDid],
|
|
3623
|
-
isGreen,
|
|
3624
|
-
});
|
|
3625
|
-
},
|
|
3626
|
-
});
|
|
3627
|
-
}
|
|
3640
|
+
const nodeInfo = await states.node.read();
|
|
3628
3641
|
|
|
3629
|
-
|
|
3630
|
-
|
|
3642
|
+
if (!skipRunningCheck) {
|
|
3643
|
+
await checkBlockletProcessHealthy(blocklet, {
|
|
3644
|
+
minConsecutiveTime,
|
|
3645
|
+
timeout,
|
|
3646
|
+
componentDids,
|
|
3631
3647
|
isGreen,
|
|
3648
|
+
appDid: did,
|
|
3649
|
+
enableDocker: nodeInfo.enableDocker,
|
|
3650
|
+
getBlocklet: () => {
|
|
3651
|
+
return this.getBlocklet(did);
|
|
3652
|
+
},
|
|
3632
3653
|
});
|
|
3633
|
-
|
|
3634
|
-
if (needUpdateBlueStatus) {
|
|
3635
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, {
|
|
3636
|
-
componentDids,
|
|
3637
|
-
isGreen: !isGreen,
|
|
3638
|
-
});
|
|
3639
|
-
}
|
|
3640
|
-
|
|
3641
|
-
const nextBlocklet = await this.getBlocklet(did);
|
|
3642
|
-
|
|
3643
|
-
await this.configSynchronizer.throttledSyncAppConfig(nextBlocklet);
|
|
3644
|
-
const componentsInfo = getComponentsInternalInfo(nextBlocklet);
|
|
3645
|
-
this.emit(BlockletInternalEvents.componentUpdated, {
|
|
3646
|
-
appDid: blocklet.appDid,
|
|
3647
|
-
components: componentsInfo,
|
|
3648
|
-
});
|
|
3649
|
-
this.emit(BlockletInternalEvents.componentStarted, {
|
|
3650
|
-
appDid: blocklet.appDid,
|
|
3651
|
-
components: (componentDids || []).map((x) => ({ did: x })),
|
|
3652
|
-
});
|
|
3653
|
-
|
|
3654
|
-
this.emit(BlockletEvents.statusChange, nextBlocklet);
|
|
3655
|
-
this.emit(BlockletEvents.started, { ...nextBlocklet, componentDids });
|
|
3656
|
-
|
|
3657
|
-
launcher.notifyBlockletStarted(blocklet);
|
|
3658
|
-
|
|
3659
|
-
logger.info('blocklet healthy', { did, name, time: Date.now() - startedAt });
|
|
3660
|
-
|
|
3661
|
-
if (needUpdateBlueStatus) {
|
|
3662
|
-
try {
|
|
3663
|
-
await this.deleteProcess({ did, componentDids, isGreen: !isGreen }, context);
|
|
3664
|
-
} catch {
|
|
3665
|
-
logger.error('delete process failed', { did, componentDids, isGreen });
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
|
|
3669
|
-
return runningRes;
|
|
3670
|
-
} catch (error) {
|
|
3671
|
-
const errorBlockletIds = [];
|
|
3672
|
-
for (const componentDid of componentDids) {
|
|
3673
|
-
if (!successBlockletIds.has(componentDid)) {
|
|
3674
|
-
errorBlockletIds.push(componentDid);
|
|
3675
|
-
}
|
|
3676
|
-
}
|
|
3677
|
-
if (errorBlockletIds.length === 0) {
|
|
3678
|
-
return this.getBlocklet(did);
|
|
3679
|
-
}
|
|
3680
|
-
|
|
3681
|
-
const status = await states.blocklet.getBlockletStatus(did);
|
|
3682
|
-
if ([BlockletStatus.stopping, BlockletStatus.stopped].includes(status)) {
|
|
3683
|
-
logger.info(`Check blocklet healthy failing because blocklet is ${fromBlockletStatus(status)}`);
|
|
3684
|
-
return {};
|
|
3685
|
-
}
|
|
3686
|
-
|
|
3687
|
-
logger.error('check blocklet if started failed', { did, name, context, timeout, error });
|
|
3688
|
-
|
|
3689
|
-
await this.deleteProcess({ did, componentDids: errorBlockletIds, isGreen }, context);
|
|
3690
|
-
|
|
3691
|
-
let doc;
|
|
3692
|
-
if (isGreen) {
|
|
3693
|
-
doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, {
|
|
3694
|
-
componentDids: errorBlockletIds,
|
|
3695
|
-
isGreen,
|
|
3696
|
-
});
|
|
3697
|
-
} else {
|
|
3698
|
-
doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids: errorBlockletIds });
|
|
3699
|
-
}
|
|
3700
|
-
|
|
3701
|
-
const componentNames = getComponentNamesWithVersion(blocklet, errorBlockletIds);
|
|
3702
|
-
const description = componentNames
|
|
3703
|
-
? `${getComponentNamesWithVersion(blocklet, errorBlockletIds)} start failed for ${getDisplayName(blocklet)}: ${
|
|
3704
|
-
error.message
|
|
3705
|
-
}`
|
|
3706
|
-
: `${blocklet.meta.title} start failed: ${error.message}`;
|
|
3707
|
-
|
|
3708
|
-
this._createNotification(did, {
|
|
3709
|
-
title: 'Component start failed',
|
|
3710
|
-
description,
|
|
3711
|
-
entityType: 'blocklet',
|
|
3712
|
-
entityId: did,
|
|
3713
|
-
severity: 'error',
|
|
3714
|
-
});
|
|
3715
|
-
|
|
3716
|
-
this.emit(BlockletEvents.startFailed, {
|
|
3717
|
-
...doc,
|
|
3718
|
-
componentDids: errorBlockletIds,
|
|
3719
|
-
error: { message: error.message },
|
|
3720
|
-
});
|
|
3721
|
-
this.emit(BlockletEvents.statusChange, { ...doc, error });
|
|
3722
|
-
|
|
3723
|
-
if (throwOnError) {
|
|
3724
|
-
throw error;
|
|
3725
|
-
}
|
|
3726
|
-
|
|
3727
|
-
return doc;
|
|
3728
3654
|
}
|
|
3729
3655
|
};
|
|
3730
3656
|
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
} = require('@blocklet/meta/lib/util');
|
|
11
11
|
const { getBlockletEngine, hasStartEngine } = require('@blocklet/meta/lib/engine');
|
|
12
12
|
const { getComponentsInternalInfo } = require('@blocklet/meta/lib/blocklet');
|
|
13
|
+
const pAll = require('p-all');
|
|
13
14
|
const {
|
|
14
15
|
forEachBlocklet,
|
|
15
16
|
validateBlocklet,
|
|
@@ -18,6 +19,7 @@ const {
|
|
|
18
19
|
getHealthyCheckTimeout,
|
|
19
20
|
getHookArgs,
|
|
20
21
|
shouldSkipComponent,
|
|
22
|
+
getComponentNamesWithVersion,
|
|
21
23
|
} = require('../../../util/blocklet');
|
|
22
24
|
const { startBlockletProcess } = require('../../../util/blocklet');
|
|
23
25
|
const hooks = require('../../hooks');
|
|
@@ -169,143 +171,256 @@ const blueGreenStartBlocklet = async (
|
|
|
169
171
|
|
|
170
172
|
const blueGreenComponentIds = await blueGreenGetComponentIds(blocklet1, entryComponentIds);
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
const startedBlockletDids = [];
|
|
175
|
+
const errorBlockletDids = [];
|
|
176
|
+
|
|
173
177
|
for (const item of blueGreenComponentIds) {
|
|
174
178
|
if (!item.componentDids.length) {
|
|
175
179
|
continue;
|
|
176
180
|
}
|
|
177
181
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
182
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
|
|
183
|
+
componentDids: item.componentDids,
|
|
184
|
+
operator,
|
|
185
|
+
isGreen: item.changeToGreen,
|
|
186
|
+
});
|
|
184
187
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
await ensureAppPortsNotOccupied({
|
|
189
|
+
blocklet: blocklet1,
|
|
190
|
+
componentDids: item.componentDids,
|
|
191
|
+
states,
|
|
192
|
+
manager,
|
|
193
|
+
isGreen: item.changeToGreen,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
192
196
|
|
|
193
|
-
|
|
197
|
+
const nextBlocklet = await manager.ensureBlocklet(did, { e2eMode });
|
|
194
198
|
|
|
195
|
-
|
|
196
|
-
const nodeEnvironments = await states.node.getEnvironments();
|
|
199
|
+
manager.emit(BlockletEvents.statusChange, nextBlocklet);
|
|
197
200
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
201
|
+
// 收集所有任务
|
|
202
|
+
const tasks = [];
|
|
203
|
+
for (const item of blueGreenComponentIds) {
|
|
204
|
+
if (!item.componentDids.length) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const subDid of item.componentDids) {
|
|
209
|
+
tasks.push(async () => {
|
|
210
|
+
try {
|
|
211
|
+
const nodeInfo = await states.node.read();
|
|
212
|
+
const nodeEnvironments = await states.node.getEnvironments();
|
|
213
|
+
|
|
214
|
+
// 钩子函数设置
|
|
215
|
+
const getHookFn =
|
|
216
|
+
(hookName) =>
|
|
217
|
+
async (b, { env }) => {
|
|
218
|
+
const hookArgs = getHookArgs(b);
|
|
219
|
+
const needRunDocker = await checkNeedRunDocker(b.meta, env, nodeInfo, isExternalBlocklet(nextBlocklet));
|
|
220
|
+
if (!b.meta.scripts?.[hookName]) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (needRunDocker) {
|
|
224
|
+
return dockerExec({
|
|
225
|
+
blocklet: nextBlocklet,
|
|
226
|
+
meta: b.meta,
|
|
227
|
+
script: b.meta.scripts?.[hookName],
|
|
228
|
+
hookName,
|
|
229
|
+
nodeInfo,
|
|
230
|
+
env,
|
|
231
|
+
...hookArgs,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return hooks[hookName](b, {
|
|
235
|
+
appDir: b.env.appDir,
|
|
236
|
+
hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
|
|
237
|
+
env,
|
|
238
|
+
did, // root blocklet did,
|
|
239
|
+
teamManager: manager.teamManager,
|
|
240
|
+
...hookArgs,
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
await startBlockletProcess(nextBlocklet, {
|
|
245
|
+
...context,
|
|
246
|
+
preFlight: getHookFn('preFlight'),
|
|
247
|
+
preStart: getHookFn('preStart'),
|
|
248
|
+
postStart: getHookFn('postStart'),
|
|
249
|
+
nodeEnvironments,
|
|
250
|
+
nodeInfo,
|
|
251
|
+
e2eMode,
|
|
252
|
+
componentDids: [subDid],
|
|
253
|
+
configSynchronizer: manager.configSynchronizer,
|
|
254
|
+
isGreen: item.changeToGreen,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// 健康检查绿色环境
|
|
258
|
+
const { startTimeout, minConsecutiveTime } = getHealthyCheckTimeout(nextBlocklet, {
|
|
259
|
+
checkHealthImmediately,
|
|
260
|
+
componentDids: [subDid],
|
|
225
261
|
});
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
262
|
+
|
|
263
|
+
await manager._onCheckIfStarted(
|
|
264
|
+
{
|
|
265
|
+
did,
|
|
266
|
+
context,
|
|
267
|
+
minConsecutiveTime,
|
|
268
|
+
timeout: startTimeout,
|
|
269
|
+
componentDids: [subDid],
|
|
270
|
+
},
|
|
271
|
+
{ throwOnError: true, isGreen: item.changeToGreen }
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// 收集成功的组件(排除已经在 errorBlockletDids 中的组件)
|
|
275
|
+
startedBlockletDids.push({ did: subDid, isGreen: item.changeToGreen });
|
|
276
|
+
|
|
277
|
+
logger.info('Green environment started successfully', {
|
|
278
|
+
did,
|
|
279
|
+
componentDids: [subDid],
|
|
280
|
+
});
|
|
281
|
+
} catch (err) {
|
|
282
|
+
const error = Array.isArray(err) ? err[0] : err;
|
|
283
|
+
logger.error('Failed to start green environment', { error, did, title: blocklet1.meta.title });
|
|
284
|
+
|
|
285
|
+
// 收集失败的组件
|
|
286
|
+
errorBlockletDids.push({ did: subDid, error, isGreen: item.changeToGreen });
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
await manager.deleteProcess({ did, componentDids: [subDid], isGreen: item.changeToGreen });
|
|
290
|
+
} catch (cleanupError) {
|
|
291
|
+
logger.error('Failed to cleanup green environment', { cleanupError });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
await pAll(tasks, { concurrency: 6 });
|
|
299
|
+
|
|
300
|
+
const lastBlocklet = await manager.ensureBlocklet(did, { e2eMode });
|
|
301
|
+
let errorDescription = '';
|
|
302
|
+
|
|
303
|
+
// 处理启动失败的组件
|
|
304
|
+
if (errorBlockletDids.length) {
|
|
305
|
+
const { error } = errorBlockletDids[0];
|
|
306
|
+
for (const item of errorBlockletDids) {
|
|
307
|
+
logger.error('Failed to start blocklet', {
|
|
308
|
+
error: item.error,
|
|
309
|
+
title: lastBlocklet.meta.title,
|
|
310
|
+
name: getComponentNamesWithVersion(lastBlocklet, [item.did]),
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
errorDescription = `${getComponentNamesWithVersion(
|
|
315
|
+
lastBlocklet,
|
|
316
|
+
errorBlockletDids.map((x) => x.did)
|
|
317
|
+
)} start failed for ${getDisplayName(lastBlocklet)}: ${errorBlockletDids.map((x) => x.error.message).join('. ')}`;
|
|
318
|
+
|
|
319
|
+
if (!ignoreErrorNotification) {
|
|
320
|
+
manager._createNotification(did, {
|
|
321
|
+
title: 'Blue-Green Deployment: Green Start Failed',
|
|
322
|
+
description: errorDescription,
|
|
323
|
+
entityType: 'blocklet',
|
|
324
|
+
entityId: did,
|
|
325
|
+
severity: 'error',
|
|
239
326
|
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const greenBlockletDids = errorBlockletDids.filter((x) => x.isGreen).map((x) => x.did);
|
|
330
|
+
const blueBlockletDids = errorBlockletDids.filter((x) => !x.isGreen).map((x) => x.did);
|
|
240
331
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
componentDids:
|
|
332
|
+
if (greenBlockletDids.length) {
|
|
333
|
+
await manager.deleteProcess({
|
|
334
|
+
did,
|
|
335
|
+
componentDids: greenBlockletDids,
|
|
336
|
+
shouldUpdateBlockletStatus: false,
|
|
337
|
+
isGreen: true,
|
|
245
338
|
});
|
|
339
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
340
|
+
componentDids: greenBlockletDids,
|
|
341
|
+
operator,
|
|
342
|
+
isGreen: true,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
246
345
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
did,
|
|
250
|
-
context,
|
|
251
|
-
minConsecutiveTime,
|
|
252
|
-
timeout: startTimeout,
|
|
253
|
-
componentDids: item.componentDids,
|
|
254
|
-
},
|
|
255
|
-
{ throwOnError: true, isGreen: item.changeToGreen, needUpdateBlueStatus: true }
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
logger.info('Green environment started successfully', {
|
|
346
|
+
if (blueBlockletDids.length) {
|
|
347
|
+
await manager.deleteProcess({
|
|
259
348
|
did,
|
|
260
|
-
componentDids:
|
|
349
|
+
componentDids: blueBlockletDids,
|
|
350
|
+
shouldUpdateBlockletStatus: false,
|
|
351
|
+
isGreen: false,
|
|
261
352
|
});
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// 如果需要把失败状态设置成 error:
|
|
269
|
-
// const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
270
|
-
// componentDids: item.componentDids,
|
|
271
|
-
// operator,
|
|
272
|
-
// isGreen: item.changeToGreen,
|
|
273
|
-
// });
|
|
274
|
-
// manager.emit(BlockletEvents.statusChange, doc1);
|
|
275
|
-
manager.emit(BlockletEvents.statusChange, blocklet1);
|
|
276
|
-
} catch (cleanupError) {
|
|
277
|
-
logger.error('Failed to cleanup green environment', { cleanupError });
|
|
278
|
-
}
|
|
353
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
354
|
+
componentDids: blueBlockletDids,
|
|
355
|
+
operator,
|
|
356
|
+
isGreen: false,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
279
359
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
});
|
|
289
|
-
}
|
|
360
|
+
const finalBlocklet = await manager.getBlocklet(did);
|
|
361
|
+
manager.emit(BlockletEvents.startFailed, {
|
|
362
|
+
...finalBlocklet,
|
|
363
|
+
componentDids: errorBlockletDids.map((x) => x.did),
|
|
364
|
+
error: { message: error.message },
|
|
365
|
+
});
|
|
366
|
+
manager.emit(BlockletEvents.statusChange, { ...finalBlocklet, error: { message: error.message } });
|
|
367
|
+
}
|
|
290
368
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
369
|
+
// 处理成功启动的组件
|
|
370
|
+
if (startedBlockletDids.length) {
|
|
371
|
+
const startedGreenBlockletDids = startedBlockletDids.filter((x) => x.isGreen).map((x) => x.did);
|
|
372
|
+
const startedBlueBlockletDids = startedBlockletDids.filter((x) => !x.isGreen).map((x) => x.did);
|
|
373
|
+
|
|
374
|
+
if (startedGreenBlockletDids.length) {
|
|
375
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
376
|
+
componentDids: startedGreenBlockletDids,
|
|
377
|
+
operator,
|
|
378
|
+
isGreen: true,
|
|
379
|
+
});
|
|
380
|
+
await manager.deleteProcess({ did, componentDids: startedGreenBlockletDids, isGreen: false });
|
|
295
381
|
}
|
|
296
382
|
|
|
297
|
-
|
|
298
|
-
|
|
383
|
+
if (startedBlueBlockletDids.length) {
|
|
384
|
+
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
385
|
+
componentDids: startedBlueBlockletDids,
|
|
386
|
+
operator,
|
|
387
|
+
isGreen: false,
|
|
388
|
+
});
|
|
389
|
+
await manager.deleteProcess({ did, componentDids: startedBlueBlockletDids, isGreen: true });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const finalBlocklet = await manager.getBlocklet(did);
|
|
393
|
+
|
|
394
|
+
await manager.configSynchronizer.throttledSyncAppConfig(finalBlocklet);
|
|
395
|
+
const componentsInfo = getComponentsInternalInfo(finalBlocklet);
|
|
396
|
+
manager.emit(BlockletInternalEvents.componentUpdated, {
|
|
397
|
+
appDid: blocklet1.appDid,
|
|
398
|
+
components: componentsInfo,
|
|
399
|
+
});
|
|
400
|
+
manager.emit(BlockletInternalEvents.componentStarted, {
|
|
401
|
+
appDid: blocklet1.appDid,
|
|
402
|
+
components: startedBlockletDids.map((x) => ({ did: x.did })),
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
manager.emit(BlockletEvents.statusChange, finalBlocklet);
|
|
406
|
+
manager.emit(BlockletEvents.started, {
|
|
407
|
+
...finalBlocklet,
|
|
408
|
+
componentDids: startedBlockletDids.map((x) => x.did),
|
|
409
|
+
});
|
|
299
410
|
}
|
|
300
411
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
412
|
+
// 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
|
|
413
|
+
if (!['true', '1'].includes(process.env.ABT_NODE_DISABLE_BLUE_GREEN)) {
|
|
414
|
+
manager.emit(BlockletEvents.blurOrGreenStarted, {
|
|
415
|
+
blocklet: lastBlocklet,
|
|
416
|
+
componentDids,
|
|
417
|
+
context,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
307
420
|
|
|
308
|
-
|
|
421
|
+
if (throwOnError && errorDescription) {
|
|
422
|
+
throw new Error(errorDescription);
|
|
423
|
+
}
|
|
309
424
|
};
|
|
310
425
|
|
|
311
426
|
module.exports = {
|
|
@@ -158,9 +158,19 @@ const blueGreenUpgradeBlocklet = async (
|
|
|
158
158
|
return blocklet;
|
|
159
159
|
} catch (err) {
|
|
160
160
|
logger.error('failed to upgrade blocklet', { did, version, name, error: err });
|
|
161
|
+
const nextBlocklet = await manager.ensureBlocklet(did);
|
|
162
|
+
for (const child of nextBlocklet.children) {
|
|
163
|
+
for (const oldChild of oldBlocklet.children) {
|
|
164
|
+
if (child && oldChild && child.meta.did === oldChild.meta.did) {
|
|
165
|
+
oldChild.status = child.status;
|
|
166
|
+
oldChild.greenStatus = child.greenStatus;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
161
170
|
await states.blocklet.upgradeBlocklet({ ...oldBlocklet });
|
|
162
171
|
manager.configSynchronizer.throttledSyncAppConfig(oldBlocklet);
|
|
163
172
|
await manager._updateDependents(did);
|
|
173
|
+
manager.emit(BlockletEvents.statusChange, { ...oldBlocklet, error: { message: err.message } });
|
|
164
174
|
|
|
165
175
|
const actionName = action === INSTALL_ACTIONS.INSTALL_COMPONENT ? 'install' : 'upgrade';
|
|
166
176
|
const notificationEvent =
|
package/lib/states/blocklet.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
/* eslint-disable no-await-in-loop */
|
|
3
3
|
/* eslint-disable function-paren-newline */
|
|
4
4
|
/* eslint-disable no-underscore-dangle */
|
|
5
|
-
const pick = require('lodash/pick');
|
|
6
5
|
const omit = require('lodash/omit');
|
|
7
6
|
const uniq = require('lodash/uniq');
|
|
8
7
|
const cloneDeep = require('@abtnode/util/lib/deep-clone');
|
|
@@ -15,7 +14,6 @@ const {
|
|
|
15
14
|
forEachBlocklet,
|
|
16
15
|
forEachBlockletSync,
|
|
17
16
|
forEachComponentV2,
|
|
18
|
-
forEachComponentV2Sync,
|
|
19
17
|
getBlockletServices,
|
|
20
18
|
hasStartEngine,
|
|
21
19
|
} = require('@blocklet/meta/lib/util');
|
|
@@ -591,14 +589,13 @@ class BlockletState extends BaseState {
|
|
|
591
589
|
return res;
|
|
592
590
|
}
|
|
593
591
|
|
|
594
|
-
|
|
595
|
-
forEachComponentV2Sync(doc, (component) => {
|
|
592
|
+
for (const component of doc.children || []) {
|
|
596
593
|
if (component.meta.group === BlockletGroup.gateway) {
|
|
597
|
-
|
|
594
|
+
continue;
|
|
598
595
|
}
|
|
599
596
|
|
|
600
597
|
if (shouldSkipComponent(component.meta.did, componentDids)) {
|
|
601
|
-
|
|
598
|
+
continue;
|
|
602
599
|
}
|
|
603
600
|
|
|
604
601
|
component[isGreen ? 'greenStatus' : 'status'] = status;
|
|
@@ -616,11 +613,16 @@ class BlockletState extends BaseState {
|
|
|
616
613
|
}
|
|
617
614
|
component.operator = operator;
|
|
618
615
|
component.inProgressStart = Date.now();
|
|
619
|
-
}
|
|
616
|
+
}
|
|
620
617
|
|
|
621
|
-
const
|
|
622
|
-
const updateData =
|
|
623
|
-
|
|
618
|
+
const shouldSetStatus = status === BlockletStatus.downloading || status === BlockletStatus.waiting;
|
|
619
|
+
const updateData = {
|
|
620
|
+
children: doc.children,
|
|
621
|
+
operator,
|
|
622
|
+
};
|
|
623
|
+
if (shouldSetStatus) {
|
|
624
|
+
updateData.status = status;
|
|
625
|
+
}
|
|
624
626
|
|
|
625
627
|
const res = await this.updateBlocklet(did, updateData);
|
|
626
628
|
return res;
|
package/lib/util/blocklet.js
CHANGED
|
@@ -781,15 +781,15 @@ const startBlockletProcess = async (
|
|
|
781
781
|
namespace: 'blocklets',
|
|
782
782
|
name: processIdName,
|
|
783
783
|
cwd,
|
|
784
|
-
// FIXME @linchen [] does not work, so use () here
|
|
785
784
|
log_date_format: '(YYYY-MM-DD HH:mm:ss)',
|
|
786
785
|
output: path.join(logsDir, 'output.log'),
|
|
787
786
|
error: path.join(logsDir, 'error.log'),
|
|
788
|
-
wait_ready: process.env.NODE_ENV !== 'test',
|
|
787
|
+
// wait_ready: process.env.NODE_ENV !== 'test',
|
|
788
|
+
wait_ready: false,
|
|
789
789
|
listen_timeout: 3000,
|
|
790
790
|
max_memory_restart: `${maxMemoryRestart}M`,
|
|
791
791
|
max_restarts: b.mode === BLOCKLET_MODES.DEVELOPMENT ? 0 : 3,
|
|
792
|
-
min_uptime:
|
|
792
|
+
min_uptime: 10_000,
|
|
793
793
|
exp_backoff_restart_delay: 300,
|
|
794
794
|
env: omit(
|
|
795
795
|
{
|
|
@@ -1142,9 +1142,52 @@ const _checkProcessHealthy = async (
|
|
|
1142
1142
|
timeout,
|
|
1143
1143
|
doConsecutiveCheck: blocklet.mode !== BLOCKLET_MODES.DEVELOPMENT,
|
|
1144
1144
|
waitTCP: !webInterface,
|
|
1145
|
+
shouldAbort: async () => {
|
|
1146
|
+
// Check if pm2 process exists and is online
|
|
1147
|
+
try {
|
|
1148
|
+
const info = await getProcessInfo(processId, { timeout: 3_000 });
|
|
1149
|
+
const currentStatus = info.pm2_env.status;
|
|
1150
|
+
if (currentStatus !== 'online') {
|
|
1151
|
+
throw new Error(`pm2 process ${processId} status is ${currentStatus}, not online`);
|
|
1152
|
+
}
|
|
1153
|
+
} catch (err) {
|
|
1154
|
+
// If process doesn't exist or has error, abort immediately
|
|
1155
|
+
logger.error('pm2 process check failed in shouldAbort', { appDid, error: err, processId, name });
|
|
1156
|
+
const isProcessNotExist =
|
|
1157
|
+
err.message &&
|
|
1158
|
+
(err.message.includes('not found') ||
|
|
1159
|
+
err.message.includes('does not exist') ||
|
|
1160
|
+
err.message.includes('not running'));
|
|
1161
|
+
if (isProcessNotExist) {
|
|
1162
|
+
throw new Error(`pm2 process ${processId} (${name}) died or does not exist: ${err.message}`);
|
|
1163
|
+
}
|
|
1164
|
+
throw new Error(`pm2 process ${processId} (${name}) check failed: ${err.message}`);
|
|
1165
|
+
}
|
|
1166
|
+
},
|
|
1145
1167
|
});
|
|
1146
1168
|
} catch (error) {
|
|
1147
|
-
|
|
1169
|
+
const isProcessDead =
|
|
1170
|
+
error.message &&
|
|
1171
|
+
(error.message.includes('pm2 process') ||
|
|
1172
|
+
error.message.includes('died') ||
|
|
1173
|
+
error.message.includes('does not exist'));
|
|
1174
|
+
if (isProcessDead) {
|
|
1175
|
+
logger.error('blocklet process died during health check', {
|
|
1176
|
+
appDid,
|
|
1177
|
+
processId,
|
|
1178
|
+
name,
|
|
1179
|
+
port,
|
|
1180
|
+
error: error.message,
|
|
1181
|
+
});
|
|
1182
|
+
throw error;
|
|
1183
|
+
}
|
|
1184
|
+
logger.error('ensure endpoint healthy failed', {
|
|
1185
|
+
appDid,
|
|
1186
|
+
port,
|
|
1187
|
+
minConsecutiveTime,
|
|
1188
|
+
timeout,
|
|
1189
|
+
error: error.message,
|
|
1190
|
+
});
|
|
1148
1191
|
throw error;
|
|
1149
1192
|
}
|
|
1150
1193
|
} catch (error) {
|
|
@@ -3,6 +3,7 @@ const fs = require('fs');
|
|
|
3
3
|
const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
|
|
4
4
|
const promiseSpawn = require('@abtnode/util/lib/promise-spawn');
|
|
5
5
|
const logger = require('@abtnode/logger')('@abtnode/docker-exec-chown');
|
|
6
|
+
const sleep = require('@abtnode/util/lib/sleep');
|
|
6
7
|
const debianChmodDockerfile = require('./debian-chmod-dockerfile');
|
|
7
8
|
const parseDockerName = require('./parse-docker-name');
|
|
8
9
|
const { checkDockerHasImage } = require('./check-docker-has-image');
|
|
@@ -65,10 +66,11 @@ async function dockerExecChown({ name, dirs, code = 777, force = false }) {
|
|
|
65
66
|
await promiseSpawn(
|
|
66
67
|
`docker rm -fv ${realName} > /dev/null 2>&1 || true && docker run --rm --name ${realName} ${volumes} ${image} sh -c '${command}'`,
|
|
67
68
|
{},
|
|
68
|
-
{ timeout: 1000 * 120, retry:
|
|
69
|
+
{ timeout: 1000 * 120, retry: 0 }
|
|
69
70
|
);
|
|
70
71
|
} finally {
|
|
71
72
|
await promiseSpawn(`docker rm -fv ${realName} > /dev/null 2>&1 || true`, {}, { timeout: 1000 * 10, retry: 3 });
|
|
73
|
+
await sleep(500);
|
|
72
74
|
await lockFile.releaseLock(realName);
|
|
73
75
|
}
|
|
74
76
|
|
|
@@ -5,6 +5,7 @@ const promiseSpawn = require('@abtnode/util/lib/promise-spawn');
|
|
|
5
5
|
const { DBCache, getAbtNodeRedisAndSQLiteUrl } = require('@abtnode/db-cache');
|
|
6
6
|
const logger = require('@abtnode/logger')('@abtnode/docker-exec');
|
|
7
7
|
|
|
8
|
+
const sleep = require('@abtnode/util/lib/sleep');
|
|
8
9
|
const parseDockerOptionsFromPm2 = require('./parse-docker-options-from-pm2');
|
|
9
10
|
const { checkDockerInstalled } = require('./check-docker-installed');
|
|
10
11
|
|
|
@@ -26,7 +27,7 @@ async function dockerExec({
|
|
|
26
27
|
runScriptParams,
|
|
27
28
|
nodeInfo,
|
|
28
29
|
timeout = 120_000,
|
|
29
|
-
retry =
|
|
30
|
+
retry = 0,
|
|
30
31
|
output,
|
|
31
32
|
error,
|
|
32
33
|
}) {
|
|
@@ -81,10 +82,11 @@ async function dockerExec({
|
|
|
81
82
|
{ timeout, retry }
|
|
82
83
|
);
|
|
83
84
|
} finally {
|
|
84
|
-
await lock.releaseLock(lockKey);
|
|
85
85
|
if (nodeInfo.isDockerInstalled) {
|
|
86
86
|
await promiseSpawn(`docker rm -f ${options.env.dockerName} > /dev/null 2>&1 || true`, {}, { timeout: 1000 * 10 });
|
|
87
87
|
}
|
|
88
|
+
await sleep(500);
|
|
89
|
+
await lock.releaseLock(lockKey);
|
|
88
90
|
}
|
|
89
91
|
logger.info(`dockerExec ${options.env.dockerName} cost time: ${Date.now() - startTime}ms`);
|
|
90
92
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.17.3-beta-
|
|
6
|
+
"version": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -17,21 +17,21 @@
|
|
|
17
17
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
18
18
|
"license": "Apache-2.0",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@abtnode/analytics": "1.17.3-beta-
|
|
21
|
-
"@abtnode/auth": "1.17.3-beta-
|
|
22
|
-
"@abtnode/certificate-manager": "1.17.3-beta-
|
|
23
|
-
"@abtnode/constant": "1.17.3-beta-
|
|
24
|
-
"@abtnode/cron": "1.17.3-beta-
|
|
25
|
-
"@abtnode/db-cache": "1.17.3-beta-
|
|
26
|
-
"@abtnode/docker-utils": "1.17.3-beta-
|
|
27
|
-
"@abtnode/logger": "1.17.3-beta-
|
|
28
|
-
"@abtnode/models": "1.17.3-beta-
|
|
29
|
-
"@abtnode/queue": "1.17.3-beta-
|
|
30
|
-
"@abtnode/rbac": "1.17.3-beta-
|
|
31
|
-
"@abtnode/router-provider": "1.17.3-beta-
|
|
32
|
-
"@abtnode/static-server": "1.17.3-beta-
|
|
33
|
-
"@abtnode/timemachine": "1.17.3-beta-
|
|
34
|
-
"@abtnode/util": "1.17.3-beta-
|
|
20
|
+
"@abtnode/analytics": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
21
|
+
"@abtnode/auth": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
22
|
+
"@abtnode/certificate-manager": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
23
|
+
"@abtnode/constant": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
24
|
+
"@abtnode/cron": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
25
|
+
"@abtnode/db-cache": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
26
|
+
"@abtnode/docker-utils": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
27
|
+
"@abtnode/logger": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
28
|
+
"@abtnode/models": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
29
|
+
"@abtnode/queue": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
30
|
+
"@abtnode/rbac": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
31
|
+
"@abtnode/router-provider": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
32
|
+
"@abtnode/static-server": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
33
|
+
"@abtnode/timemachine": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
34
|
+
"@abtnode/util": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
35
35
|
"@aigne/aigne-hub": "^0.10.9",
|
|
36
36
|
"@arcblock/did": "^1.27.7",
|
|
37
37
|
"@arcblock/did-connect-js": "^1.27.7",
|
|
@@ -43,15 +43,15 @@
|
|
|
43
43
|
"@arcblock/pm2-events": "^0.0.5",
|
|
44
44
|
"@arcblock/validator": "^1.27.7",
|
|
45
45
|
"@arcblock/vc": "^1.27.7",
|
|
46
|
-
"@blocklet/constant": "1.17.3-beta-
|
|
46
|
+
"@blocklet/constant": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
47
47
|
"@blocklet/did-space-js": "^1.2.4",
|
|
48
|
-
"@blocklet/env": "1.17.3-beta-
|
|
48
|
+
"@blocklet/env": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
49
49
|
"@blocklet/error": "^0.3.3",
|
|
50
|
-
"@blocklet/meta": "1.17.3-beta-
|
|
51
|
-
"@blocklet/resolver": "1.17.3-beta-
|
|
52
|
-
"@blocklet/sdk": "1.17.3-beta-
|
|
53
|
-
"@blocklet/server-js": "1.17.3-beta-
|
|
54
|
-
"@blocklet/store": "1.17.3-beta-
|
|
50
|
+
"@blocklet/meta": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
51
|
+
"@blocklet/resolver": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
52
|
+
"@blocklet/sdk": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
53
|
+
"@blocklet/server-js": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
54
|
+
"@blocklet/store": "1.17.3-beta-20251121-135300-8e451e6e",
|
|
55
55
|
"@blocklet/theme": "^3.2.6",
|
|
56
56
|
"@fidm/x509": "^1.2.1",
|
|
57
57
|
"@ocap/mcrypto": "^1.27.7",
|
|
@@ -116,5 +116,5 @@
|
|
|
116
116
|
"express": "^4.18.2",
|
|
117
117
|
"unzipper": "^0.10.11"
|
|
118
118
|
},
|
|
119
|
-
"gitHead": "
|
|
119
|
+
"gitHead": "e34d7abbe02a8b6eb754b7e2590a9e3d9d663c7d"
|
|
120
120
|
}
|