kuzzle 2.60.0-beta.1 → 2.60.0-beta.3

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.
Files changed (179) hide show
  1. package/dist/bin/copy-binaries.js +17 -7
  2. package/dist/bin/start-kuzzle-server +19 -8
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js +6 -2
  5. package/dist/lib/api/controllers/adminController.d.ts +9 -3
  6. package/dist/lib/api/controllers/adminController.js +42 -26
  7. package/dist/lib/api/controllers/authController.d.ts +15 -5
  8. package/dist/lib/api/controllers/authController.js +35 -20
  9. package/dist/lib/api/controllers/baseController.js +17 -7
  10. package/dist/lib/api/controllers/bulkController.js +17 -7
  11. package/dist/lib/api/controllers/collectionController.js +17 -7
  12. package/dist/lib/api/controllers/debugController.js +17 -7
  13. package/dist/lib/api/controllers/documentController.js +17 -7
  14. package/dist/lib/api/controllers/indexController.d.ts +1 -1
  15. package/dist/lib/api/controllers/memoryStorageController.js +17 -7
  16. package/dist/lib/api/controllers/securityController.d.ts +30 -28
  17. package/dist/lib/api/controllers/securityController.js +18 -8
  18. package/dist/lib/api/controllers/serverController.d.ts +0 -1
  19. package/dist/lib/api/controllers/serverController.js +17 -7
  20. package/dist/lib/api/documentExtractor.js +17 -7
  21. package/dist/lib/api/funnel.js +19 -9
  22. package/dist/lib/api/httpRoutes.d.ts +2 -1
  23. package/dist/lib/api/openapi/OpenApiManager.d.ts +2 -1
  24. package/dist/lib/api/openapi/openApiGenerator.js +1 -2
  25. package/dist/lib/api/request/kuzzleRequest.d.ts +9 -2
  26. package/dist/lib/api/request/kuzzleRequest.js +21 -7
  27. package/dist/lib/api/request/requestContext.d.ts +7 -2
  28. package/dist/lib/api/request/requestContext.js +17 -7
  29. package/dist/lib/api/request/requestInput.d.ts +1 -1
  30. package/dist/lib/api/request/requestInput.js +17 -7
  31. package/dist/lib/api/request/requestResponse.d.ts +2 -2
  32. package/dist/lib/api/request/requestResponse.js +21 -10
  33. package/dist/lib/cluster/command.d.ts +0 -2
  34. package/dist/lib/cluster/command.js +17 -7
  35. package/dist/lib/cluster/idCardHandler.d.ts +7 -2
  36. package/dist/lib/cluster/idCardHandler.js +24 -3
  37. package/dist/lib/cluster/node.d.ts +15 -3
  38. package/dist/lib/cluster/node.js +112 -40
  39. package/dist/lib/cluster/protobuf/command.proto +23 -22
  40. package/dist/lib/cluster/protobuf/sync.proto +91 -85
  41. package/dist/lib/cluster/publisher.js +17 -7
  42. package/dist/lib/cluster/state.js +17 -7
  43. package/dist/lib/cluster/subscriber.d.ts +0 -2
  44. package/dist/lib/cluster/subscriber.js +17 -7
  45. package/dist/lib/cluster/workers/IDCardRenewer.d.ts +6 -1
  46. package/dist/lib/cluster/workers/IDCardRenewer.js +14 -8
  47. package/dist/lib/config/index.js +20 -14
  48. package/dist/lib/core/auth/formatProcessing.d.ts +1 -1
  49. package/dist/lib/core/auth/formatProcessing.js +3 -3
  50. package/dist/lib/core/auth/passportResponse.d.ts +0 -1
  51. package/dist/lib/core/auth/passportWrapper.js +24 -8
  52. package/dist/lib/core/backend/backend.js +17 -7
  53. package/dist/lib/core/backend/backendConfig.d.ts +1 -1
  54. package/dist/lib/core/backend/backendConfig.js +17 -7
  55. package/dist/lib/core/backend/backendController.js +17 -7
  56. package/dist/lib/core/backend/backendErrors.js +17 -7
  57. package/dist/lib/core/backend/backendHook.js +17 -7
  58. package/dist/lib/core/backend/backendImport.js +17 -7
  59. package/dist/lib/core/backend/backendPipe.d.ts +4 -1
  60. package/dist/lib/core/backend/backendPipe.js +17 -7
  61. package/dist/lib/core/backend/backendPlugin.js +17 -7
  62. package/dist/lib/core/backend/backendStorage.js +8 -8
  63. package/dist/lib/core/backend/backendSubscription.js +17 -7
  64. package/dist/lib/core/backend/backendVault.js +17 -7
  65. package/dist/lib/core/backend/internalLogger.d.ts +7 -7
  66. package/dist/lib/core/debug/kuzzleDebugger.js +17 -7
  67. package/dist/lib/core/network/accessLogger.d.ts +0 -1
  68. package/dist/lib/core/network/accessLogger.js +17 -7
  69. package/dist/lib/core/network/clientConnection.js +17 -7
  70. package/dist/lib/core/network/context.d.ts +0 -1
  71. package/dist/lib/core/network/entryPoint.js +17 -7
  72. package/dist/lib/core/network/httpRouter/routePart.js +17 -7
  73. package/dist/lib/core/network/protocols/httpMessage.d.ts +0 -2
  74. package/dist/lib/core/network/protocols/httpwsProtocol.d.ts +0 -3
  75. package/dist/lib/core/network/protocols/httpwsProtocol.js +17 -7
  76. package/dist/lib/core/network/protocols/mqttProtocol.d.ts +1 -2
  77. package/dist/lib/core/network/protocols/mqttProtocol.js +31 -10
  78. package/dist/lib/core/network/router.js +17 -7
  79. package/dist/lib/core/plugin/plugin.js +17 -7
  80. package/dist/lib/core/plugin/pluginContext.d.ts +20 -9
  81. package/dist/lib/core/plugin/pluginContext.js +39 -24
  82. package/dist/lib/core/plugin/pluginsManager.js +17 -7
  83. package/dist/lib/core/realtime/channel.js +17 -7
  84. package/dist/lib/core/realtime/hotelClerk.d.ts +3 -2
  85. package/dist/lib/core/realtime/hotelClerk.js +17 -7
  86. package/dist/lib/core/realtime/notifier.js +17 -7
  87. package/dist/lib/core/security/profileRepository.js +17 -7
  88. package/dist/lib/core/security/roleRepository.js +17 -7
  89. package/dist/lib/core/security/securityLoader.js +17 -7
  90. package/dist/lib/core/security/tokenRepository.js +18 -8
  91. package/dist/lib/core/security/userRepository.js +17 -7
  92. package/dist/lib/core/shared/ObjectRepository.js +20 -7
  93. package/dist/lib/core/shared/abstractManifest.js +17 -7
  94. package/dist/lib/core/shared/sdk/embeddedSdk.js +20 -8
  95. package/dist/lib/core/shared/sdk/funnelProtocol.js +17 -7
  96. package/dist/lib/core/shared/store.d.ts +11 -1
  97. package/dist/lib/core/shared/store.js +26 -30
  98. package/dist/lib/core/statistics/statistics.d.ts +0 -1
  99. package/dist/lib/core/statistics/statistics.js +17 -7
  100. package/dist/lib/core/storage/clientAdapter.d.ts +4 -6
  101. package/dist/lib/core/storage/clientAdapter.js +24 -18
  102. package/dist/lib/core/storage/indexCache.js +17 -7
  103. package/dist/lib/core/validation/specification.js +1 -2
  104. package/dist/lib/core/validation/types/date.js +17 -7
  105. package/dist/lib/core/validation/types/enum.js +17 -7
  106. package/dist/lib/core/validation/types/geoShape.js +17 -7
  107. package/dist/lib/core/validation/types/geoShapeUtils.js +6 -7
  108. package/dist/lib/core/validation/validation.js +17 -7
  109. package/dist/lib/core/validation/validationUtils.js +24 -15
  110. package/dist/lib/kerror/codes/8-cluster.json +23 -0
  111. package/dist/lib/kerror/codes/index.js +20 -10
  112. package/dist/lib/kerror/errors/kuzzleError.js +18 -8
  113. package/dist/lib/kerror/index.js +31 -18
  114. package/dist/lib/kuzzle/Logger.js +4 -3
  115. package/dist/lib/kuzzle/dumpGenerator.js +17 -7
  116. package/dist/lib/kuzzle/event/KuzzleEventEmitter.d.ts +3 -3
  117. package/dist/lib/kuzzle/event/KuzzleEventEmitter.js +17 -7
  118. package/dist/lib/kuzzle/event/pipeRunner.js +17 -7
  119. package/dist/lib/kuzzle/exitOnFailure.d.ts +13 -0
  120. package/dist/lib/kuzzle/exitOnFailure.js +60 -0
  121. package/dist/lib/kuzzle/internalIndexHandler.js +37 -32
  122. package/dist/lib/kuzzle/kuzzle.d.ts +20 -8
  123. package/dist/lib/kuzzle/kuzzle.js +148 -130
  124. package/dist/lib/model/security/profile.js +17 -7
  125. package/dist/lib/model/security/role.js +17 -7
  126. package/dist/lib/model/security/user.js +17 -7
  127. package/dist/lib/model/storage/apiKey.d.ts +1 -1
  128. package/dist/lib/model/storage/apiKey.js +17 -7
  129. package/dist/lib/service/cache/redis.js +40 -9
  130. package/dist/lib/service/service.js +17 -7
  131. package/dist/lib/service/storage/7/elasticsearch.d.ts +36 -25
  132. package/dist/lib/service/storage/7/elasticsearch.js +73 -55
  133. package/dist/lib/service/storage/8/elasticsearch.d.ts +47 -36
  134. package/dist/lib/service/storage/8/elasticsearch.js +73 -55
  135. package/dist/lib/service/storage/Elasticsearch.d.ts +10 -4
  136. package/dist/lib/types/HttpStream.d.ts +0 -1
  137. package/dist/lib/types/Plugin.js +17 -7
  138. package/dist/lib/types/config/SecurityConfiguration.d.ts +0 -2
  139. package/dist/lib/types/config/storageEngine/StorageEngineElasticsearchConfiguration.d.ts +5 -0
  140. package/dist/lib/types/controllers/GetCurrentUserResponse.d.ts +5 -0
  141. package/dist/lib/types/controllers/GetCurrentUserResponse.js +3 -0
  142. package/dist/lib/types/controllers/ResetSecurityResult.d.ts +5 -0
  143. package/dist/lib/types/controllers/ResetSecurityResult.js +3 -0
  144. package/dist/lib/types/controllers/adminController.type.d.ts +7 -5
  145. package/dist/lib/types/controllers/authController.type.d.ts +7 -5
  146. package/dist/lib/types/core/auth/Serialized.d.ts +14 -0
  147. package/dist/lib/types/core/auth/Serialized.js +3 -0
  148. package/dist/lib/types/core/auth/formatProcessing.type.d.ts +5 -11
  149. package/dist/lib/types/events/EventProtocol.d.ts +0 -2
  150. package/dist/lib/types/index.d.ts +1 -0
  151. package/dist/lib/types/index.js +1 -0
  152. package/dist/lib/types/storage/8/Elasticsearch.d.ts +4 -3
  153. package/dist/lib/util/array.d.ts +1 -1
  154. package/dist/lib/util/array.js +7 -6
  155. package/dist/lib/util/assertType.js +5 -6
  156. package/dist/lib/util/async.js +1 -2
  157. package/dist/lib/util/asyncStore.d.ts +0 -1
  158. package/dist/lib/util/bufferedPassThrough.d.ts +0 -3
  159. package/dist/lib/util/crypto.js +1 -2
  160. package/dist/lib/util/debug.d.ts +0 -1
  161. package/dist/lib/util/debug.js +17 -7
  162. package/dist/lib/util/distributedLock.d.ts +27 -4
  163. package/dist/lib/util/distributedLock.js +69 -16
  164. package/dist/lib/util/dump-collection.d.ts +2 -2
  165. package/dist/lib/util/dump-collection.js +22 -13
  166. package/dist/lib/util/esRequest.js +1 -2
  167. package/dist/lib/util/extractFields.d.ts +16 -7
  168. package/dist/lib/util/extractFields.js +1 -10
  169. package/dist/lib/util/koncordeCompat.js +22 -13
  170. package/dist/lib/util/mutex.d.ts +4 -4
  171. package/dist/lib/util/mutex.js +21 -11
  172. package/dist/lib/util/name-generator.js +2 -2
  173. package/dist/lib/util/requestAssertions.js +27 -18
  174. package/dist/lib/util/safeObject.js +3 -4
  175. package/dist/lib/util/stackTrace.js +2 -3
  176. package/dist/lib/util/thrown.js +1 -2
  177. package/dist/lib/util/time.js +1 -2
  178. package/dist/package.json +22 -15
  179. package/package.json +22 -15
@@ -17,6 +17,12 @@ import { Logger } from "./Logger";
17
17
  declare class Kuzzle extends KuzzleEventEmitter {
18
18
  config: IKuzzleConfiguration;
19
19
  private _state;
20
+ /** The running shutdown, which later `shutdown()` calls wait for (#3017) */
21
+ private shutdownPromise;
22
+ /** The highest exit code asked for by the `shutdown()` calls so far */
23
+ private shutdownExitCode;
24
+ /** Ends a failure's shutdown that does not finish in time */
25
+ private shutdownWatchdog;
20
26
  log: Logger;
21
27
  /** Read by `PluginsManager` to locate the plugins directories. */
22
28
  rootPath: string;
@@ -95,8 +101,12 @@ declare class Kuzzle extends KuzzleEventEmitter {
95
101
  * @param {Object} - Additional options (import, installations, plugins, secretsFile, support, vaultKey)
96
102
  *
97
103
  * @this {Kuzzle}
104
+ *
105
+ * The public signature takes `unknown`, which every call that compiled with
106
+ * v2.56.0's `any` still satisfies. `Backend.start()` is the one caller, and
107
+ * it hands the application wrapper the implementation reads.
98
108
  */
99
- start(application: any, options?: StartOptions): Promise<void>;
109
+ start(application: unknown, options?: StartOptions): Promise<void>;
100
110
  /**
101
111
  * Generates the node ID.
102
112
  *
@@ -113,6 +123,7 @@ declare class Kuzzle extends KuzzleEventEmitter {
113
123
  * @returns {Promise}
114
124
  */
115
125
  shutdown(exitCode?: number): Promise<void>;
126
+ private runShutdown;
116
127
  /**
117
128
  * Execute multiple handlers only once on any given environment
118
129
  *
@@ -121,18 +132,19 @@ declare class Kuzzle extends KuzzleEventEmitter {
121
132
  * @returns {Promise<void>}
122
133
  */
123
134
  install(installations?: InstallationConfig[]): Promise<void>;
124
- ask(event: string, ...args: [payload?: any, ...rest: any]): Promise<any>;
125
- emit(event: string, ...args: any[]): boolean;
126
- pipe(event: string, ...args: any[]): Promise<any>;
135
+ ask(event: string, ...args: [payload?: unknown, ...rest: unknown[]]): Promise<any>;
136
+ emit(event: string, ...args: unknown[]): boolean;
137
+ pipe(event: string, ...args: unknown[]): Promise<any>;
127
138
  private importUserMappings;
128
139
  private importMappings;
129
140
  private importFixtures;
130
141
  private importPermissions;
142
+ private isConfigsEmpty;
131
143
  /**
132
- * Check if every import has been done, if one of them is not finished yet, wait for it
144
+ * Records the hash of the payload just imported (or found already imported)
145
+ * in both Redis and `%kuzzle.imports`, so that the next node or the next
146
+ * start can tell the import is done for this very payload
133
147
  */
134
- private _waitForImportToFinish;
135
- private isConfigsEmpty;
136
148
  private persistHashedImport;
137
149
  /**
138
150
  * Load into the app several imports
@@ -144,7 +156,7 @@ declare class Kuzzle extends KuzzleEventEmitter {
144
156
  */
145
157
  loadInitialState(toImport?: ImportConfig, toSupport?: SupportConfig): Promise<void>;
146
158
  dump(suffix: string): Promise<string>;
147
- hash(input: any): number;
159
+ hash(input: unknown): number;
148
160
  get state(): kuzzleStateEnum;
149
161
  set state(value: kuzzleStateEnum);
150
162
  /**
@@ -35,13 +35,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
35
35
  }) : function(o, v) {
36
36
  o["default"] = v;
37
37
  });
38
- var __importStar = (this && this.__importStar) || function (mod) {
39
- if (mod && mod.__esModule) return mod;
40
- var result = {};
41
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
42
- __setModuleDefault(result, mod);
43
- return result;
44
- };
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
45
55
  var __importDefault = (this && this.__importDefault) || function (mod) {
46
56
  return (mod && mod.__esModule) ? mod : { "default": mod };
47
57
  };
@@ -73,6 +83,7 @@ const validation_1 = __importDefault(require("../core/validation/validation"));
73
83
  const kerror = __importStar(require("../kerror"));
74
84
  const asyncStore_1 = __importDefault(require("../util/asyncStore"));
75
85
  const crypto_1 = require("../util/crypto");
86
+ const distributedLock_1 = require("../util/distributedLock");
76
87
  const mutex_1 = require("../util/mutex");
77
88
  const dumpGenerator_1 = __importDefault(require("./dumpGenerator"));
78
89
  const KuzzleEventEmitter_1 = __importDefault(require("./event/KuzzleEventEmitter"));
@@ -82,6 +93,12 @@ const Logger_1 = require("./Logger");
82
93
  const vault_1 = __importDefault(require("./vault"));
83
94
  const name_generator_1 = require("../util/name-generator");
84
95
  let _kuzzle = null;
96
+ /**
97
+ * How long a shutdown caused by a failure may run before the process exits
98
+ * anyway: a `kuzzle:shutdown` pipe or a lock release that never settles must
99
+ * not keep a failed node alive, which a bare `process.exit(1)` never did
100
+ */
101
+ const FAILURE_SHUTDOWN_TIMEOUT = 30000;
85
102
  Reflect.defineProperty(global, "kuzzle", {
86
103
  configurable: true,
87
104
  enumerable: false,
@@ -102,6 +119,12 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
102
119
  constructor(config) {
103
120
  super(config.plugins.common.maxConcurrentPipes, config.plugins.common.pipesBufferSize);
104
121
  this._state = kuzzleStateEnum_1.default.STARTING;
122
+ /** The running shutdown, which later `shutdown()` calls wait for (#3017) */
123
+ this.shutdownPromise = null;
124
+ /** The highest exit code asked for by the `shutdown()` calls so far */
125
+ this.shutdownExitCode = 0;
126
+ /** Ends a failure's shutdown that does not finish in time */
127
+ this.shutdownWatchdog = null;
105
128
  global.kuzzle = this;
106
129
  this._state = kuzzleStateEnum_1.default.STARTING;
107
130
  this.config = config;
@@ -138,15 +161,7 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
138
161
  userMappings: this.importUserMappings.bind(this),
139
162
  };
140
163
  }
141
- /**
142
- * Initializes all the needed components of Kuzzle.
143
- *
144
- * @param {Application} - Application Plugin instance
145
- * @param {Object} - Additional options (import, installations, plugins, secretsFile, support, vaultKey)
146
- *
147
- * @this {Kuzzle}
148
- */
149
- async start(application, options = { import: {} }) {
164
+ async start(application, options) {
150
165
  this.registerSignalHandlers();
151
166
  try {
152
167
  this.log.info(`[ℹ] Starting Kuzzle ${this.version} ...`);
@@ -164,7 +179,7 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
164
179
  await new security_1.default().init();
165
180
  // This will init the cluster module if enabled
166
181
  await this.initKuzzleNode();
167
- this.vault = vault_1.default.load(options.vaultKey, options.secretsFile, this.config.vault?.newAlgorithm);
182
+ this.vault = vault_1.default.load(options?.vaultKey, options?.secretsFile, this.config.vault?.newAlgorithm);
168
183
  this.validation.init();
169
184
  await this.tokenManager.init();
170
185
  await this.funnel.init();
@@ -175,15 +190,15 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
175
190
  await this.entryPoint.init();
176
191
  await this.debugger.init();
177
192
  this.pluginsManager.application = application;
178
- const pluginImports = await this.pluginsManager.init(options.plugins);
179
- const imports = lodash_1.default.merge({}, pluginImports, options.import);
193
+ const pluginImports = await this.pluginsManager.init(options?.plugins);
194
+ const imports = lodash_1.default.merge({}, pluginImports, options?.import);
180
195
  // Authentification plugins must be loaded before users import to avoid
181
196
  // credentials related error which would prevent Kuzzle from starting
182
- await this.loadInitialState(imports, options.support);
197
+ await this.loadInitialState(imports, options?.support);
183
198
  await this.ask("core:security:verify");
184
199
  this.router.init();
185
200
  this.log.info("[✔] Core components loaded");
186
- await this.install(options.installations);
201
+ await this.install(options?.installations);
187
202
  // `application` is the very wrapper handed to the plugins manager six
188
203
  // lines up, so reading the name back off the manager was a round trip
189
204
  // through a getter that can answer `undefined`.
@@ -201,6 +216,16 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
201
216
  }
202
217
  catch (error) {
203
218
  this.log.error(`[X] Cannot start Kuzzle ${this.version}: ${error instanceof Error ? error.message : (0, node_util_1.inspect)(error)}`);
219
+ // A failed start cannot be retried (a second Kuzzle instance cannot be
220
+ // built), and what it did start keeps the process alive: in production,
221
+ // a node with no listener and an ID card still renewed (#3020). Not
222
+ // awaited, so the caller still gets the error before the process exits.
223
+ this.shutdown(1).catch((shutdownError) => {
224
+ this.log.error(`Unable to shut down after a failed start: ${shutdownError instanceof Error
225
+ ? shutdownError.message
226
+ : (0, node_util_1.inspect)(shutdownError)}`);
227
+ process.exit(1);
228
+ });
204
229
  throw error;
205
230
  }
206
231
  }
@@ -228,6 +253,22 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
228
253
  * @returns {Promise}
229
254
  */
230
255
  async shutdown(exitCode = 0) {
256
+ // Several sources can ask for it at once (a signal, a cluster eviction,
257
+ // the `exit` handler that `process.exit` below fires): they share the one
258
+ // shutdown, and a failure reported by any of them keeps the exit non-zero
259
+ this.shutdownExitCode = Math.max(this.shutdownExitCode, exitCode);
260
+ // Not for a requested shutdown: it waits for the requests still running,
261
+ // however long they take
262
+ if (exitCode !== 0) {
263
+ this.shutdownWatchdog ?? (this.shutdownWatchdog = setTimeout(() => {
264
+ this.log.error(`[shutdown] Not finished after ${FAILURE_SHUTDOWN_TIMEOUT}ms: exiting now`);
265
+ process.exit(this.shutdownExitCode);
266
+ }, FAILURE_SHUTDOWN_TIMEOUT));
267
+ }
268
+ this.shutdownPromise ?? (this.shutdownPromise = this.runShutdown());
269
+ return this.shutdownPromise;
270
+ }
271
+ async runShutdown() {
231
272
  this._state = kuzzleStateEnum_1.default.SHUTTING_DOWN;
232
273
  this.log.info("Initiating shutdown...");
233
274
  // Ask the network layer to stop accepting new request
@@ -241,7 +282,10 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
241
282
  }
242
283
  // The other nodes may be waiting on a lock this one holds: a node leaving
243
284
  // mid-startup is often in the middle of one (F-13).
244
- await mutex_1.Mutex.releaseAllBeforeExit(); // NOSONAR: frees locks taken through the deprecated API
285
+ await Promise.all([
286
+ (0, distributedLock_1.releaseAllLocksBeforeExit)(),
287
+ mutex_1.Mutex.releaseAllBeforeExit(), // NOSONAR: frees locks taken through the deprecated API
288
+ ]);
245
289
  this.log.info("Halted.");
246
290
  // flush both application and Kuzzle core loggers before leaving (Could happen even if some core/application components are not initialized)
247
291
  await this?.log?.flush?.();
@@ -251,7 +295,10 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
251
295
  // `pluginsManager?.application?.log?.flush?.()` was a silent no-op and the
252
296
  // application's buffered logs were dropped on shutdown (TD-51).
253
297
  await this?.pluginsManager?.application?.instance?.log?.flush?.();
254
- process.exit(exitCode);
298
+ if (this.shutdownWatchdog !== null) {
299
+ clearTimeout(this.shutdownWatchdog);
300
+ }
301
+ process.exit(this.shutdownExitCode);
255
302
  }
256
303
  /**
257
304
  * Execute multiple handlers only once on any given environment
@@ -266,9 +313,9 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
266
313
  if (!installations?.length) {
267
314
  return;
268
315
  }
269
- const mutex = new mutex_1.Mutex("backend:installations");
270
- await mutex.lock();
271
- try {
316
+ // No retry limit: another node is running the installations, and this
317
+ // one must not start before they are recorded
318
+ await (0, distributedLock_1.withLock)("backend:installations", async () => {
272
319
  for (const installation of installations) {
273
320
  const isAlreadyInstalled = await this.ask("core:storage:private:document:exist", "kuzzle", "installations", installation.id);
274
321
  if (!isAlreadyInstalled) {
@@ -286,10 +333,7 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
286
333
  this.log.info(`[✔] Install code "${installation.id}" successfully executed`);
287
334
  }
288
335
  }
289
- }
290
- finally {
291
- await mutex.unlock();
292
- }
336
+ }, { retryAttempts: Number.POSITIVE_INFINITY });
293
337
  }
294
338
  // For testing purpose
295
339
  async ask(event, ...args) {
@@ -322,11 +366,8 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
322
366
  }
323
367
  else if (!lodash_1.default.isEmpty(toSupport.mappings)) {
324
368
  await this.ask("core:storage:public:mappings:import", toSupport.mappings, {
325
- /**
326
- * If it's the first time the mapping are loaded and another node is already importing the mapping into the database
327
- * we just want to load the mapping in our own index cache and not in the database.
328
- */
329
- indexCacheOnly: status.initialized || !status.locked,
369
+ // Already imported (by this node or another one): only fill the cache
370
+ indexCacheOnly: status.initialized,
330
371
  propagate: false, // Each node needs to do the import themselves
331
372
  rawMappings: true,
332
373
  refresh: true,
@@ -335,11 +376,8 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
335
376
  }
336
377
  else if (!lodash_1.default.isEmpty(toImport.mappings)) {
337
378
  await this.ask("core:storage:public:mappings:import", toImport.mappings, {
338
- /**
339
- * If it's the first time the mapping are loaded and another node is already importing the mapping into the database
340
- * we just want to load the mapping in our own index cache and not in the database.
341
- */
342
- indexCacheOnly: status.initialized || !status.locked,
379
+ // Already imported (by this node or another one): only fill the cache
380
+ indexCacheOnly: status.initialized,
343
381
  propagate: false, // Each node needs to do the import themselves
344
382
  refresh: true,
345
383
  });
@@ -392,19 +430,6 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
392
430
  this.log.info("[✔] Permissions import successful");
393
431
  }
394
432
  }
395
- /**
396
- * Check if every import has been done, if one of them is not finished yet, wait for it
397
- */
398
- async _waitForImportToFinish() {
399
- const importTypes = Object.keys(this.importTypes);
400
- for (const importType of importTypes) {
401
- // If the import is done, we pop it from the queue to check the next one
402
- if (await this.ask("core:cache:internal:get", `backend:init:import:${importType}`)) {
403
- return;
404
- }
405
- await bluebird_1.default.delay(1000);
406
- }
407
- }
408
433
  isConfigsEmpty(importConfig, supportConfig) {
409
434
  if (lodash_1.default.isEmpty(importConfig.mappings) &&
410
435
  lodash_1.default.isEmpty(importConfig.profiles) &&
@@ -418,10 +443,16 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
418
443
  }
419
444
  return false;
420
445
  }
421
- async persistHashedImport({ existingRedisHash, existingESHash, importPayloadHash, type, }) {
422
- if (!existingRedisHash && !existingESHash) {
423
- // If the import is not initialized in the redis cache and in the ES, we initialize it
424
- this.log.info(`${type} import is not initialized, initializing...`);
446
+ /**
447
+ * Records the hash of the payload just imported (or found already imported)
448
+ * in both Redis and `%kuzzle.imports`, so that the next node or the next
449
+ * start can tell the import is done for this very payload
450
+ */
451
+ async persistHashedImport({ existingRedisHash, existingESHash, importPayloadHash, initialized, type, }) {
452
+ // Not initialized: the payload was just imported, and a hash left by a
453
+ // previous payload must be replaced, or every start would import it again
454
+ if (!initialized || !existingESHash) {
455
+ this.log.info(`Recording the ${type} import in %kuzzle.imports...`);
425
456
  // `createOrReplace`, not `create`: this document has a fixed id, so a
426
457
  // conflict on it means another node initialized the same import — an
427
458
  // outcome to converge on, not an error to die on. `create` made that
@@ -429,24 +460,9 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
429
460
  await this.ask("core:storage:private:document:createOrReplace", "kuzzle", "imports", `backend:init:import:${type}`, {
430
461
  hash: importPayloadHash,
431
462
  });
432
- await this.ask("core:cache:internal:store", `backend:init:import:${type}`, importPayloadHash);
433
- }
434
- else if (existingRedisHash && !existingESHash) {
435
- // If the import is initialized in the redis cache but not in the ES
436
- // We initialize it in the ES
437
- this.log.info(`${type} import is not initialized in %kuzzle.imports, initializing...`);
438
- const redisCache = await this.ask("core:cache:internal:get", `backend:init:import:${type}`);
439
- // Same reasoning as above: a fixed id, so a conflict is convergence.
440
- await this.ask("core:storage:private:document:createOrReplace", "kuzzle", "imports", `backend:init:import:${type}`, {
441
- hash: redisCache,
442
- });
443
463
  }
444
- else if (!existingRedisHash && existingESHash) {
445
- // If the import is initialized in the ES but not in the redis cache
446
- // We initialize it in the redis cache
447
- this.log.info(`${type} import is not initialized in the redis cache, initializing...`);
448
- const esDocument = await this.ask("core:storage:private:document:get", "kuzzle", "imports", `backend:init:import:${type}`);
449
- await this.ask("core:cache:internal:store", `backend:init:import:${type}`, esDocument._source.hash);
464
+ if (!initialized || !existingRedisHash) {
465
+ await this.ask("core:cache:internal:store", `backend:init:import:${type}`, importPayloadHash);
450
466
  }
451
467
  }
452
468
  /**
@@ -461,42 +477,37 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
461
477
  if (this.isConfigsEmpty(toImport, toSupport)) {
462
478
  return;
463
479
  }
464
- const lockedMutex = [];
465
- try {
466
- for (const [type, importMethod] of Object.entries(this.importTypes)) {
467
- const importPayload = {};
468
- switch (type) {
469
- case "fixtures":
470
- lodash_1.default.set(importPayload, "toSupport.fixtures", toSupport.fixtures);
471
- break;
472
- case "mappings":
473
- lodash_1.default.set(importPayload, "toSupport.mappings", toSupport.mappings);
474
- lodash_1.default.set(importPayload, "toImport.mappings", toImport.mappings);
475
- break;
476
- case "permissions":
477
- lodash_1.default.set(importPayload, "toSupport.securities", toSupport.securities);
478
- lodash_1.default.set(importPayload, "toImport.profiles", toImport.profiles);
479
- lodash_1.default.set(importPayload, "toImport.roles", toImport.roles);
480
- lodash_1.default.set(importPayload, "toImport.users", toImport.users);
481
- break;
482
- }
483
- // `?? ""`: `json-stable-stringify` answers undefined for a value it
484
- // cannot serialise, and the payload is built right above.
485
- const importPayloadHash = (0, crypto_1.sha256)((0, json_stable_stringify_1.default)(importPayload) ?? "");
486
- // `timeout: 0` means a single acquisition attempt: exactly one node
487
- // runs the import and the others carry on with `locked: false`.
488
- // The TTL has to outlive the import itself, because this lock is held
489
- // until the `finally` below — across every import type and the wait
490
- // that follows. It is still an upper bound rather than a guarantee,
491
- // which is why the bookkeeping write below is idempotent (TD-69).
492
- // NOSONAR: `Mutex` is deprecated in favour of `withLock`, but the two
493
- // use incompatible acquisition/TTL formats and must not contend on the
494
- // same key — every other node still takes this lock with `Mutex`, so
495
- // swapping one site would remove the exclusion it exists for. Deferred
496
- // to #2894, like the remaining call sites.
497
- const lockOptions = { timeout: 0, ttl: 60000 };
498
- const mutex = new mutex_1.Mutex(`backend:import:${type}`, lockOptions); // NOSONAR
499
- const locked = await mutex.lock();
480
+ const importTypes = Object.entries(this.importTypes);
481
+ // Imports the types from `index` on. Each type's lock is taken around the
482
+ // remaining types, so that every lock taken is held until all the imports
483
+ // are done: a node waiting for the first lock starts only once the node
484
+ // ahead of it has imported everything.
485
+ const importFrom = async (index) => {
486
+ const next = importTypes[index];
487
+ if (!next) {
488
+ return;
489
+ }
490
+ const [type, importMethod] = next;
491
+ const importPayload = {};
492
+ switch (type) {
493
+ case "fixtures":
494
+ lodash_1.default.set(importPayload, "toSupport.fixtures", toSupport.fixtures);
495
+ break;
496
+ case "mappings":
497
+ lodash_1.default.set(importPayload, "toSupport.mappings", toSupport.mappings);
498
+ lodash_1.default.set(importPayload, "toImport.mappings", toImport.mappings);
499
+ break;
500
+ case "permissions":
501
+ lodash_1.default.set(importPayload, "toSupport.securities", toSupport.securities);
502
+ lodash_1.default.set(importPayload, "toImport.profiles", toImport.profiles);
503
+ lodash_1.default.set(importPayload, "toImport.roles", toImport.roles);
504
+ lodash_1.default.set(importPayload, "toImport.users", toImport.users);
505
+ break;
506
+ }
507
+ // `?? ""`: `json-stable-stringify` answers undefined for a value it
508
+ // cannot serialise, and the payload is built right above.
509
+ const importPayloadHash = (0, crypto_1.sha256)((0, json_stable_stringify_1.default)(importPayload) ?? "");
510
+ const importType = async () => {
500
511
  // Read the bookkeeping *after* the lock, never before: a read taken
501
512
  // before acquiring it is not protected by it, and the holder that
502
513
  // acted on a stale "not initialized" used to create a document the
@@ -515,26 +526,31 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
515
526
  initialized = esDocument._source.hash === importPayloadHash;
516
527
  }
517
528
  await importMethod({ toImport, toSupport }, {
518
- firstCall: !initialized && locked,
519
- initialized: initialized,
520
- locked,
529
+ firstCall: !initialized,
530
+ initialized,
521
531
  });
522
- if (locked) {
523
- lockedMutex.push(mutex);
524
- await this.persistHashedImport({
525
- existingESHash,
526
- existingRedisHash,
527
- importPayloadHash,
528
- type,
529
- });
530
- }
531
- }
532
- await this._waitForImportToFinish();
533
- this.log.info("[✔] Import successful");
534
- }
535
- finally {
536
- await Promise.all(lockedMutex.map((mutex) => mutex.unlock()));
537
- }
532
+ await this.persistHashedImport({
533
+ existingESHash,
534
+ existingRedisHash,
535
+ importPayloadHash,
536
+ initialized,
537
+ type,
538
+ });
539
+ await importFrom(index + 1);
540
+ };
541
+ // No retry limit: the node holding it is running this import, and this
542
+ // one must not start before it is done. Once it gets the lock, it finds
543
+ // the import done for this payload and only fills its cache; if the
544
+ // node ahead died mid-import, it runs the import itself. The lock is
545
+ // refreshed while held; a lost lock fails the startup with
546
+ // `MutexLockLostError` (the bookkeeping write stays idempotent, TD-69).
547
+ await (0, distributedLock_1.withLock)(`backend:import:${type}`, importType, {
548
+ retryAttempts: Number.POSITIVE_INFINITY,
549
+ ttl: 60000,
550
+ });
551
+ };
552
+ await importFrom(0);
553
+ this.log.info("[✔] Import successful");
538
554
  }
539
555
  dump(suffix) {
540
556
  return this.dumpGenerator.dump(suffix);
@@ -633,7 +649,9 @@ class Kuzzle extends KuzzleEventEmitter_1.default {
633
649
  // nothing to do with that error
634
650
  }
635
651
  }
636
- await this.shutdown();
652
+ // Only abnormal terminations get here: exit non-zero, so that an
653
+ // `on-failure` restart policy brings the node back (#3020)
654
+ await this.shutdown(1);
637
655
  }
638
656
  }
639
657
  exports.Kuzzle = Kuzzle;
@@ -35,13 +35,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
35
35
  }) : function(o, v) {
36
36
  o["default"] = v;
37
37
  });
38
- var __importStar = (this && this.__importStar) || function (mod) {
39
- if (mod && mod.__esModule) return mod;
40
- var result = {};
41
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
42
- __setModuleDefault(result, mod);
43
- return result;
44
- };
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
45
55
  var __importDefault = (this && this.__importDefault) || function (mod) {
46
56
  return (mod && mod.__esModule) ? mod : { "default": mod };
47
57
  };
@@ -35,13 +35,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
35
35
  }) : function(o, v) {
36
36
  o["default"] = v;
37
37
  });
38
- var __importStar = (this && this.__importStar) || function (mod) {
39
- if (mod && mod.__esModule) return mod;
40
- var result = {};
41
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
42
- __setModuleDefault(result, mod);
43
- return result;
44
- };
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
45
55
  Object.defineProperty(exports, "__esModule", { value: true });
46
56
  exports.Role = void 0;
47
57
  const kerror = __importStar(require("../../kerror"));
@@ -35,13 +35,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
35
35
  }) : function(o, v) {
36
36
  o["default"] = v;
37
37
  });
38
- var __importStar = (this && this.__importStar) || function (mod) {
39
- if (mod && mod.__esModule) return mod;
40
- var result = {};
41
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
42
- __setModuleDefault(result, mod);
43
- return result;
44
- };
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
45
55
  var __importDefault = (this && this.__importDefault) || function (mod) {
46
56
  return (mod && mod.__esModule) ? mod : { "default": mod };
47
57
  };
@@ -12,7 +12,7 @@ declare class ApiKey extends BaseModel {
12
12
  userId: string;
13
13
  description: string;
14
14
  expiresAt: number;
15
- ttl: number | string;
15
+ ttl: number;
16
16
  token: string;
17
17
  fingerprint: string;
18
18
  constructor(_source: JSONObject, _id?: string | null);
@@ -35,13 +35,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
35
35
  }) : function(o, v) {
36
36
  o["default"] = v;
37
37
  });
38
- var __importStar = (this && this.__importStar) || function (mod) {
39
- if (mod && mod.__esModule) return mod;
40
- var result = {};
41
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
42
- __setModuleDefault(result, mod);
43
- return result;
44
- };
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
45
55
  var __importDefault = (this && this.__importDefault) || function (mod) {
46
56
  return (mod && mod.__esModule) ? mod : { "default": mod };
47
57
  };