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
@@ -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.RequestResponse = exports.Headers = void 0;
47
57
  const assert = __importStar(require("../../util/assertType"));
@@ -72,11 +82,11 @@ class Headers {
72
82
  if (typeof name === "symbol") {
73
83
  return this.headers[name];
74
84
  }
75
- assert.assertString("header name", name);
76
- if (!name) {
85
+ const header = assert.assertString("header name", name);
86
+ if (!header) {
77
87
  return;
78
88
  }
79
- const storedName = this.namesMap.get(name.toLowerCase());
89
+ const storedName = this.namesMap.get(header.toLowerCase());
80
90
  return storedName === undefined ? undefined : this.headers[storedName];
81
91
  }
82
92
  removeHeader(name) {
@@ -239,6 +249,7 @@ class RequestResponse {
239
249
  /**
240
250
  * Request result
241
251
  */
252
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- kept for v2 compatibility, see docs/v3-breaking-changes.md
242
253
  get result() {
243
254
  return this[_request].result;
244
255
  }
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  import Long from "long";
4
2
  import type { SerializedState } from "./state";
5
3
  import type { Activity, Decoded, FullStateResponse, HandshakeResponse } from "./protobuf/commandMessages";
@@ -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
  };
@@ -1,4 +1,5 @@
1
1
  import "../types";
2
+ import type ClusterNode from "./node";
2
3
  export type SerializedIdCard = {
3
4
  id: string;
4
5
  ip: string;
@@ -41,7 +42,7 @@ export declare class ClusterIdCardHandler {
41
42
  /**
42
43
  * Node instance. Represents the local node.
43
44
  */
44
- private node;
45
+ private readonly node;
45
46
  /**
46
47
  * Local node ID Card
47
48
  */
@@ -85,7 +86,11 @@ export declare class ClusterIdCardHandler {
85
86
  */
86
87
  private disposed;
87
88
  private readonly logger;
88
- constructor(node: any);
89
+ constructor(node: ClusterNode);
90
+ /**
91
+ * How long an ID card outlives the last renewal of its node, in ms
92
+ */
93
+ get ttl(): number;
89
94
  /**
90
95
  * Generates and reserves a unique ID for this node instance.
91
96
  * Makes sure that the ID is not already taken by another node instance.
@@ -94,6 +94,12 @@ class ClusterIdCardHandler {
94
94
  this.ip = node.ip;
95
95
  this.refreshDelay = node.heartbeatDelay;
96
96
  }
97
+ /**
98
+ * How long an ID card outlives the last renewal of its node, in ms
99
+ */
100
+ get ttl() {
101
+ return this.refreshDelay * this.refreshMultiplier;
102
+ }
97
103
  /**
98
104
  * Generates and reserves a unique ID for this node instance.
99
105
  * Makes sure that the ID is not already taken by another node instance.
@@ -139,7 +145,8 @@ class ClusterIdCardHandler {
139
145
  const refreshWorker = this.constructWorker(`${__dirname}/workers/IDCardRenewer${(0, node_path_1.extname)(__filename)}`);
140
146
  this.refreshWorker = refreshWorker;
141
147
  refreshWorker.on("message", async (message) => {
142
- if (message.error) {
148
+ // A renewal racing the disposal finds the card deleted: not a failure
149
+ if (message.error && !this.disposed) {
143
150
  await this.node.evictSelf(message.error);
144
151
  }
145
152
  });
@@ -207,7 +214,7 @@ class ClusterIdCardHandler {
207
214
  // this method runs on the line after it is filled.
208
215
  refreshWorker.on("message", ({ initialized }) => {
209
216
  if (initialized) {
210
- clearInterval(this.refreshTimer);
217
+ clearInterval(this.refreshTimer ?? undefined);
211
218
  this.refreshTimer = null;
212
219
  }
213
220
  });
@@ -228,6 +235,20 @@ class ClusterIdCardHandler {
228
235
  // It could happens that the worker has been killed before the dispose causing send to fail
229
236
  }
230
237
  }
238
+ clearInterval(this.refreshTimer ?? undefined);
239
+ this.refreshTimer = null;
240
+ // The worker deletes the card too, but nothing waits for it: the process
241
+ // usually exits first, and the card then outlives it by a TTL, long
242
+ // enough for a node restarted on this IP to take it for a live duplicate
243
+ if (this.nodeIdKey !== null) {
244
+ try {
245
+ await global.kuzzle.ask("core:cache:internal:del", this.nodeIdKey);
246
+ await global.kuzzle.ask("core:cache:internal:execute", "srem", REDIS_ID_CARDS_INDEX, this.nodeIdKey);
247
+ }
248
+ catch (error) {
249
+ this.logger.warn(`Could not delete the ID card ${this.nodeIdKey}: it expires in ${this.ttl}ms (${error instanceof Error ? error.message : String(error)})`);
250
+ }
251
+ }
231
252
  }
232
253
  /**
233
254
  * Retrieves the ID cards from other nodes
@@ -309,7 +330,7 @@ class ClusterIdCardHandler {
309
330
  if (!this.idCard) {
310
331
  return false;
311
332
  }
312
- return global.kuzzle.ask("core:cache:internal:store", this.nodeIdKey, JSON.stringify(this.idCard.serialize()), { onlyIfNew: creation, ttl: this.refreshDelay * this.refreshMultiplier });
333
+ return global.kuzzle.ask("core:cache:internal:store", this.nodeIdKey, JSON.stringify(this.idCard.serialize()), { onlyIfNew: creation, ttl: this.ttl });
313
334
  }
314
335
  }
315
336
  exports.ClusterIdCardHandler = ClusterIdCardHandler;
@@ -143,16 +143,28 @@ declare class ClusterNode {
143
143
  *
144
144
  * Extracted from `handshake()` unchanged.
145
145
  *
146
- * @returns false when the caller must stop — the cluster is split and this
147
- * node is shutting down
146
+ * @throws {cluster.join.full_state_unavailable} on the second failure: the
147
+ * cluster is split
148
148
  */
149
149
  private retryFullState;
150
+ /**
151
+ * The ID cards of the other nodes, none of which has this node's IP.
152
+ *
153
+ * A card with this IP may be the stale one of a previous run of this node
154
+ * (a process killed, a container restarted at once): it expires within a
155
+ * TTL, so it is only a real duplicate if it is still there after that.
156
+ *
157
+ * @throws {cluster.join.duplicate_ip} if it is
158
+ */
159
+ private discoverNodes;
150
160
  /**
151
161
  * Discovers other active nodes from the cluster and, if other nodes exist,
152
162
  * starts a handshake procedure to sync this node and to make it able to
153
163
  * handle new client requests
154
164
  *
155
- * @return {void}
165
+ * Rejects with a `cluster.join.*` error, and shuts the node down, when it
166
+ * cannot join: the node must not go on starting as if it had (a duplicate
167
+ * IP, a split cluster, `joinTimeout` reached).
156
168
  */
157
169
  handshake(): Promise<void>;
158
170
  /**
@@ -19,6 +19,39 @@
19
19
  * See the License for the specific language governing permissions and
20
20
  * limitations under the License.
21
21
  */
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
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
+ })();
22
55
  var __importDefault = (this && this.__importDefault) || function (mod) {
23
56
  return (mod && mod.__esModule) ? mod : { "default": mod };
24
57
  };
@@ -37,19 +70,19 @@ const eventemitter3_1 = __importDefault(require("eventemitter3"));
37
70
  const intersection_1 = __importDefault(require("lodash/intersection"));
38
71
  const xor_1 = __importDefault(require("lodash/xor"));
39
72
  const long_1 = __importDefault(require("long"));
73
+ const kerror = __importStar(require("../kerror"));
74
+ const kuzzleError_1 = require("../kerror/errors/kuzzleError");
40
75
  const kuzzleStateEnum_1 = __importDefault(require("../kuzzle/kuzzleStateEnum"));
41
76
  const debug_1 = __importDefault(require("../util/debug"));
42
77
  const koncordeCompat_1 = require("../util/koncordeCompat");
43
- // NOSONAR: `Mutex` is deprecated in favour of `withLock`, but the two use
44
- // incompatible acquisition/TTL formats and must not contend on the same key —
45
- // every node takes "clusterHandshake" with `Mutex`. Deferred to #2894.
46
- const mutex_1 = require("../util/mutex"); // NOSONAR
78
+ const distributedLock_1 = require("../util/distributedLock");
47
79
  const command_1 = __importDefault(require("./command"));
48
80
  const idCardHandler_1 = require("./idCardHandler");
49
81
  const publisher_1 = __importDefault(require("./publisher"));
50
82
  const state_1 = __importDefault(require("./state"));
51
83
  const subscriber_1 = __importDefault(require("./subscriber"));
52
84
  const debug = (0, debug_1.default)("kuzzle:cluster:sync");
85
+ const joinError = kerror.wrap("cluster", "join");
53
86
  /** Cluster-wide events carrying the collection write locks (see registerEvents) */
54
87
  const COLLECTION_LOCK_EVENT = "core:storage:collection:lock";
55
88
  const COLLECTION_UNLOCK_EVENT = "core:storage:collection:unlock";
@@ -335,7 +368,7 @@ class ClusterNode {
335
368
  //
336
369
  // Exit code 1: this is a failure, and a node that exits 0 is not restarted
337
370
  // by an `on-failure` policy (#2785).
338
- global.kuzzle.shutdown(1);
371
+ void global.kuzzle.shutdown(1);
339
372
  }
340
373
  /**
341
374
  * Evicts a remote from the list
@@ -391,14 +424,15 @@ class ClusterNode {
391
424
  const candidates = this.electShutdownCandidates(splits, idCards);
392
425
  if (candidates.includes(this.nodeId)) {
393
426
  this.logger.error("[CLUSTER] Network split detected. This node is outside the cluster: shutting down.");
394
- global.kuzzle.shutdown();
427
+ // A failure, like `evictSelf`'s: exit non-zero (#2785)
428
+ void global.kuzzle.shutdown(1);
395
429
  return;
396
430
  }
397
431
  }
398
432
  catch (err) {
399
433
  this.logger.error("[CLUSTER] Unexpected exception caught during a cluster consistency check. Shutting down...");
400
434
  this.logger.error(err instanceof Error ? err.stack : (0, util_1.inspect)(err));
401
- global.kuzzle.shutdown();
435
+ void global.kuzzle.shutdown(1);
402
436
  }
403
437
  }
404
438
  /**
@@ -526,8 +560,8 @@ class ClusterNode {
526
560
  *
527
561
  * Extracted from `handshake()` unchanged.
528
562
  *
529
- * @returns false when the caller must stop — the cluster is split and this
530
- * node is shutting down
563
+ * @throws {cluster.join.full_state_unavailable} on the second failure: the
564
+ * cluster is split
531
565
  */
532
566
  async retryFullState(retried) {
533
567
  // Uh oh... no node was able to give us the full state.
@@ -536,9 +570,7 @@ class ClusterNode {
536
570
  // means we're probably facing a network split, and we must then shut
537
571
  // down.
538
572
  if (retried) {
539
- this.logger.error("[CLUSTER] Could not connect to discovered cluster nodes (network split detected). Shutting down.");
540
- global.kuzzle.shutdown();
541
- return false;
573
+ throw joinError.get("full_state_unavailable");
542
574
  }
543
575
  // Disposes all subscribers
544
576
  for (const subscriber of this.remoteNodes.values()) {
@@ -549,28 +581,59 @@ class ClusterNode {
549
581
  const retryDelay = this.heartbeatDelay * 1.5;
550
582
  this.logger.warn(`[CLUSTER] Unable to connect to discovered cluster nodes. Retrying in ${retryDelay}ms...`);
551
583
  await bluebird_1.default.delay(retryDelay);
552
- return true;
584
+ }
585
+ /**
586
+ * The ID cards of the other nodes, none of which has this node's IP.
587
+ *
588
+ * A card with this IP may be the stale one of a previous run of this node
589
+ * (a process killed, a container restarted at once): it expires within a
590
+ * TTL, so it is only a real duplicate if it is still there after that.
591
+ *
592
+ * @throws {cluster.join.duplicate_ip} if it is
593
+ */
594
+ async discoverNodes() {
595
+ const nodes = await this.idCardHandler.getRemoteIdCards();
596
+ if (!nodes.some((node) => node.ip === this.ip)) {
597
+ return nodes;
598
+ }
599
+ // A heartbeat more than the TTL: the card's last renewal may be in flight
600
+ const delay = this.idCardHandler.ttl + this.heartbeatDelay;
601
+ this.logger.warn(`[CLUSTER] Another node has the same IP address as this one (${this.ip}). Waiting ${delay}ms for it to expire, in case it is a stale ID card...`);
602
+ await bluebird_1.default.delay(delay);
603
+ const remaining = await this.idCardHandler.getRemoteIdCards();
604
+ const duplicate = remaining.find((node) => node.ip === this.ip);
605
+ if (duplicate) {
606
+ throw joinError.get("duplicate_ip", this.ip, duplicate.id);
607
+ }
608
+ return remaining;
553
609
  }
554
610
  /**
555
611
  * Discovers other active nodes from the cluster and, if other nodes exist,
556
612
  * starts a handshake procedure to sync this node and to make it able to
557
613
  * handle new client requests
558
614
  *
559
- * @return {void}
615
+ * Rejects with a `cluster.join.*` error, and shuts the node down, when it
616
+ * cannot join: the node must not go on starting as if it had (a duplicate
617
+ * IP, a split cluster, `joinTimeout` reached).
560
618
  */
561
619
  async handshake() {
562
- const handshakeTimeout = setTimeout(() => {
563
- this.logger.error(`[CLUSTER] Failed to join the cluster: timed out (joinTimeout: ${this.config.joinTimeout}ms)`);
564
- global.kuzzle.shutdown();
565
- }, this.config.joinTimeout);
566
- // NOSONAR (S1874): same deferral as the import above — every node takes
567
- // "clusterHandshake" with `Mutex`, so converting one site to `withLock`
568
- // would remove the exclusion it exists for. #2894.
569
- const lockOptions = { timeout: this.config.joinTimeout };
570
- const mutex = new mutex_1.Mutex("clusterHandshake", lockOptions); // NOSONAR
571
- try {
572
- await mutex.lock();
573
- // Create the ID Key AFTER the handshake mutex is actually locked,
620
+ let timedOut = false;
621
+ let handshakeTimeout;
622
+ const timeout = new Promise((_resolve, reject) => {
623
+ handshakeTimeout = setTimeout(() => {
624
+ timedOut = true;
625
+ reject(joinError.get("timeout", this.config.joinTimeout));
626
+ }, this.config.joinTimeout);
627
+ });
628
+ // No retry limit: the handshakes are taken one at a time, and the
629
+ // `joinTimeout` above is what gives up on a cluster that never lets this
630
+ // node in.
631
+ const join = (0, distributedLock_1.withLock)("clusterHandshake", async () => {
632
+ // Given up on while waiting for the lock: do not join after all
633
+ if (timedOut) {
634
+ return;
635
+ }
636
+ // Create the ID Key AFTER the handshake lock is actually taken,
574
637
  // to prevent race conditions (other nodes attempting to connect to this
575
638
  // node while it's still initializing)
576
639
  await this.idCardHandler.createIdCard();
@@ -581,37 +644,29 @@ class ClusterNode {
581
644
  await this.startHeartbeat(); // NOSONAR: TD-26
582
645
  debug("[CLUSTER] Start heartbeat");
583
646
  let retried = false;
584
- let fullState = null;
647
+ let fullState;
585
648
  let nodes;
586
649
  debug("[CLUSTER] Start retrieving full state..");
587
650
  do {
588
- nodes = await this.idCardHandler.getRemoteIdCards();
651
+ nodes = await this.discoverNodes();
589
652
  debug("[CLUSTER] %s remote nodes discovered", nodes.length);
590
653
  // No other nodes detected = no handshake required
591
654
  if (nodes.length === 0) {
592
655
  this.trackActivity(this.nodeId, this.ip, nodeActivityEnum.ADDED);
593
656
  return;
594
657
  }
595
- // Verify that no other node share the same IP address as this one
596
- const duplicate = nodes.filter((node) => node.ip === this.ip);
597
- if (duplicate.length > 0) {
598
- this.logger.error(`[CLUSTER] Another node share the same IP address as this one (${this.ip}): ${duplicate[0]?.id}. Shutting down.`);
599
- global.kuzzle.shutdown();
600
- return;
601
- }
602
658
  await this.subscribeToNodes(nodes);
603
659
  debug("[CLUSTER] Successfully subscribed to nodes");
604
660
  fullState = await this.command.getFullState(nodes);
605
661
  if (fullState === null) {
606
- if (!(await this.retryFullState(retried))) {
607
- return;
608
- }
662
+ // Sequential by design: the retry waits for a heartbeat round
663
+ await this.retryFullState(retried); // NOSONAR
609
664
  retried = true;
610
665
  }
611
666
  } while (fullState === null);
612
667
  debug("[CLUSTER] Fullstate retrieved, loading into node..");
613
668
  await this.fullState.loadFullState(fullState); // NOSONAR: TD-26
614
- this.activity = fullState.activity ? fullState.activity : this.activity;
669
+ this.activity = fullState.activity ?? this.activity;
615
670
  debug("[CLUSTER] Fullstate loaded.");
616
671
  const handshakeResponses = await this.command.broadcastHandshake(nodes);
617
672
  debug("[CLUSTER] Successful handshakes with other nodes.");
@@ -625,10 +680,27 @@ class ClusterNode {
625
680
  ? `[CLUSTER] Successfully joined the cluster as "${this.nodeId}" (process node id: ${global.nodeId}).`
626
681
  : `[CLUSTER] Successfully joined the cluster as "${this.nodeId}".`);
627
682
  this.trackActivity(this.nodeId, this.ip, nodeActivityEnum.ADDED);
683
+ }, { retryAttempts: Number.POSITIVE_INFINITY });
684
+ try {
685
+ // A timeout does not stop `join`: the node it settles later is shutting
686
+ // down already
687
+ await Promise.race([join, timeout]);
688
+ }
689
+ catch (error) {
690
+ // The handshake cannot be stopped where it is: it may still introduce
691
+ // this node to the others. Leave the cluster rather than stay in it
692
+ // half-joined, whatever the application does with the failed startup.
693
+ if (error instanceof distributedLock_1.MutexLockLostError) {
694
+ this.logger.error("[CLUSTER] Lost the handshake lock while joining the cluster. Shutting down.");
695
+ }
696
+ if (error instanceof distributedLock_1.MutexLockLostError ||
697
+ (error instanceof kuzzleError_1.KuzzleError && error.id.startsWith("cluster.join."))) {
698
+ void global.kuzzle.shutdown(1);
699
+ }
700
+ throw error;
628
701
  }
629
702
  finally {
630
703
  clearTimeout(handshakeTimeout);
631
- await mutex.unlock();
632
704
  }
633
705
  }
634
706
  /**
@@ -1,5 +1,6 @@
1
1
  // messages definition for issuing node commands and receiving the
2
2
  // corresponding responses
3
+ // /!\ Every singular scalar field is "optional": see sync.proto's header.
3
4
  package command;
4
5
 
5
6
  syntax="proto3";
@@ -10,36 +11,36 @@ import "sync.proto";
10
11
  message FullStateResponse {
11
12
  // Describes the nodes that have subscribed to a given realtime room
12
13
  message NodeSubscribers {
13
- string nodeId = 1;
14
- uint32 subscribers = 2;
15
- uint64 messageId = 3;
14
+ optional string nodeId = 1;
15
+ optional uint32 subscribers = 2;
16
+ optional uint64 messageId = 3;
16
17
  }
17
18
 
18
19
  // Describes a realtime room
19
20
  message RealtimeRoom {
20
- string roomId = 1;
21
- string index = 2;
22
- string collection = 3;
21
+ optional string roomId = 1;
22
+ optional string index = 2;
23
+ optional string collection = 3;
23
24
 
24
25
  // array of polymorphic objects, needs to be stringified beforehand until
25
26
  // this PR is merged: https://github.com/protobufjs/protobuf.js/pull/1495
26
- string filters = 4;
27
+ optional string filters = 4;
27
28
 
28
29
  repeated NodeSubscribers nodes = 5;
29
30
  }
30
31
 
31
32
  // Describes a cluster activity event
32
33
  message Activity {
33
- string id = 1;
34
- string date = 2;
35
- string address = 3;
36
- uint32 event = 4;
34
+ optional string id = 1;
35
+ optional string date = 2;
36
+ optional string address = 3;
37
+ optional uint32 event = 4;
37
38
  optional string reason = 5;
38
39
  }
39
40
 
40
41
  message NodeState {
41
- string id = 1;
42
- uint64 lastMessageId = 2;
42
+ optional string id = 1;
43
+ optional uint64 lastMessageId = 2;
43
44
  }
44
45
 
45
46
  repeated RealtimeRoom rooms = 1;
@@ -50,30 +51,30 @@ message FullStateResponse {
50
51
 
51
52
  // Handshake Request
52
53
  message HandshakeRequest {
53
- string nodeId = 1;
54
- uint64 lastMessageId = 2;
55
- string ip = 3;
54
+ optional string nodeId = 1;
55
+ optional uint64 lastMessageId = 2;
56
+ optional string ip = 3;
56
57
  }
57
58
 
58
59
  // Handshake response
59
60
  message HandshakeResponse {
60
- bool added = 1;
61
- uint64 lastMessageId = 2;
61
+ optional bool added = 1;
62
+ optional uint64 lastMessageId = 2;
62
63
  }
63
64
 
64
65
  // Retransmit request: the messages a subscriber missed from this node's sync
65
66
  // stream, by message id, both ends included (#2785)
66
67
  message RetransmitRequest {
67
- uint64 from = 1;
68
- uint64 to = 2;
68
+ optional uint64 from = 1;
69
+ optional uint64 to = 2;
69
70
  }
70
71
 
71
72
  // Retransmit response: the missed messages, in order, exactly as they were
72
73
  // published (topic + encoded sync message)
73
74
  message RetransmitResponse {
74
75
  message Frame {
75
- string topic = 1;
76
- bytes data = 2;
76
+ optional string topic = 1;
77
+ optional bytes data = 2;
77
78
  }
78
79
 
79
80
  repeated Frame frames = 1;