kuzzle 2.60.0-beta.2 → 2.60.0-beta.4

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 (40) hide show
  1. package/dist/bin/start-kuzzle-server +2 -1
  2. package/dist/lib/api/controllers/documentController.d.ts +1 -1
  3. package/dist/lib/cluster/command.d.ts +7 -0
  4. package/dist/lib/cluster/command.js +67 -28
  5. package/dist/lib/cluster/idCardHandler.d.ts +4 -0
  6. package/dist/lib/cluster/idCardHandler.js +23 -2
  7. package/dist/lib/cluster/node.d.ts +28 -5
  8. package/dist/lib/cluster/node.js +159 -76
  9. package/dist/lib/cluster/subscriber.d.ts +2 -0
  10. package/dist/lib/cluster/subscriber.js +6 -0
  11. package/dist/lib/cluster/workers/IDCardRenewer.d.ts +6 -0
  12. package/dist/lib/cluster/workers/IDCardRenewer.js +14 -8
  13. package/dist/lib/core/auth/passportWrapper.js +3 -1
  14. package/dist/lib/core/backend/backend.d.ts +1 -1
  15. package/dist/lib/core/backend/backend.js +15 -15
  16. package/dist/lib/core/backend/backendCluster.d.ts +1 -1
  17. package/dist/lib/core/shared/store.js +4 -0
  18. package/dist/lib/core/storage/clientAdapter.d.ts +6 -0
  19. package/dist/lib/core/storage/clientAdapter.js +35 -5
  20. package/dist/lib/kerror/codes/8-cluster.json +23 -0
  21. package/dist/lib/kuzzle/exitOnFailure.d.ts +13 -0
  22. package/dist/lib/kuzzle/exitOnFailure.js +60 -0
  23. package/dist/lib/kuzzle/internalIndexHandler.js +24 -6
  24. package/dist/lib/kuzzle/kuzzle.d.ts +7 -0
  25. package/dist/lib/kuzzle/kuzzle.js +45 -2
  26. package/dist/lib/model/security/profile.d.ts +1 -1
  27. package/dist/lib/model/security/role.d.ts +1 -1
  28. package/dist/lib/model/security/user.d.ts +1 -1
  29. package/dist/lib/service/storage/7/elasticsearch.d.ts +12 -0
  30. package/dist/lib/service/storage/7/elasticsearch.js +32 -1
  31. package/dist/lib/service/storage/7/esWrapper.js +31 -2
  32. package/dist/lib/service/storage/8/elasticsearch.d.ts +12 -0
  33. package/dist/lib/service/storage/8/elasticsearch.js +32 -1
  34. package/dist/lib/service/storage/8/esWrapper.js +31 -2
  35. package/dist/lib/types/events/EventGenericDocument.d.ts +2 -1
  36. package/dist/lib/types/events/EventProtocol.d.ts +1 -1
  37. package/dist/lib/util/mutex.d.ts +2 -1
  38. package/dist/lib/util/mutex.js +7 -1
  39. package/dist/package.json +1 -1
  40. package/package.json +1 -1
@@ -89,6 +89,9 @@ const HIDDEN_COLLECTION = "_kuzzle_keep";
89
89
  // is refreshed while held, so a live holder is never overtaken; a crashed
90
90
  // one leaves it for at most `ttl` (30 s).
91
91
  const HIDDEN_COLLECTION_LOCK = { retryAttempts: Number.POSITIVE_INFINITY };
92
+ // How long `waitForCollectionShards` lets Elasticsearch wait for a
93
+ // collection's primary shards, which is also Elasticsearch's own default
94
+ const COLLECTION_SHARDS_TIMEOUT = "30s";
92
95
  const ALIAS_PREFIX = "@"; // @todo next major release: Add ALIAS_PREFIX in FORBIDDEN_CHARS
93
96
  const PRIVATE_PREFIX = "%";
94
97
  const PUBLIC_PREFIX = "&";
@@ -1168,6 +1171,9 @@ class ES8 {
1168
1171
  this._checkDynamicProperty(mappings);
1169
1172
  const exists = await this.hasCollection(index, collection);
1170
1173
  if (exists) {
1174
+ // Another node created it, maybe so recently that its primary shards
1175
+ // are still initializing
1176
+ await this.waitForCollectionShards(index, collection);
1171
1177
  return this.updateCollection(index, collection, { mappings, settings });
1172
1178
  }
1173
1179
  this._checkMappings(mappings);
@@ -1189,7 +1195,9 @@ class ES8 {
1189
1195
  if (lodash_1.default.get(error, "meta.body.error.type") ===
1190
1196
  "resource_already_exists_exception") {
1191
1197
  // race condition: the indice has been created between the "exists"
1192
- // check above and this "create" attempt
1198
+ // check above and this "create" attempt, by a request that may not
1199
+ // have waited for its shards yet
1200
+ await this.waitForCollectionShards(index, collection);
1193
1201
  return null;
1194
1202
  }
1195
1203
  throw this._esWrapper.formatESError(error);
@@ -1209,6 +1217,29 @@ class ES8 {
1209
1217
  }
1210
1218
  return null;
1211
1219
  }
1220
+ /**
1221
+ * Waits until the primary shards of a collection are active, for at most
1222
+ * `timeout`, and rejects if they are not by then.
1223
+ *
1224
+ * A collection another node has just created is in the cluster state, so
1225
+ * it exists, before its primary shard is active: until then, Elasticsearch
1226
+ * answers 503 to any document request on it. The node that creates a
1227
+ * collection waits for its shards (`wait_for_active_shards`); a node that
1228
+ * finds it already there calls this instead. It answers at once when the
1229
+ * shards are already active.
1230
+ */
1231
+ async waitForCollectionShards(index, collection, timeout = COLLECTION_SHARDS_TIMEOUT) {
1232
+ try {
1233
+ await this._client.cluster.health({
1234
+ index: this._getAlias(index, collection),
1235
+ timeout,
1236
+ wait_for_status: "yellow",
1237
+ });
1238
+ }
1239
+ catch (error) {
1240
+ throw this._esWrapper.formatESError(error);
1241
+ }
1242
+ }
1212
1243
  /**
1213
1244
  * Retrieves settings definition for index/type
1214
1245
  *
@@ -35,6 +35,35 @@ const kerror = (0, kerror_1.wrap)("services", "storage");
35
35
  function isResponseError(error) {
36
36
  return Boolean(error.meta);
37
37
  }
38
+ /**
39
+ * What the client puts in `message` when the response carried nothing it could
40
+ * read: this placeholder, or an empty string for a bodyless response.
41
+ */
42
+ const CLIENT_PLACEHOLDER_MESSAGE = "Response Error";
43
+ function isUsableMessage(message) {
44
+ return (typeof message === "string" &&
45
+ message.trim() !== "" &&
46
+ message !== CLIENT_PLACEHOLDER_MESSAGE);
47
+ }
48
+ /**
49
+ * A message for an error that came with none worth keeping (typically a 503
50
+ * with no body while shards initialize), built from what the error still
51
+ * carries: the client's error name, the HTTP status and the response body.
52
+ */
53
+ function describeUnreadableError(error) {
54
+ const statusCode = error.meta?.statusCode;
55
+ const body = error.meta?.body;
56
+ const hasBody = body !== undefined && body !== null && body !== "";
57
+ let description = statusCode === undefined
58
+ ? "Elasticsearch request failed"
59
+ : `Elasticsearch responded ${statusCode}`;
60
+ description += hasBody
61
+ ? `: ${(0, node_util_1.inspect)(body, { breakLength: Infinity, depth: 3 })}`
62
+ : " (no body)";
63
+ return error.name && error.name !== "Error"
64
+ ? `${error.name}: ${description}`
65
+ : description;
66
+ }
38
67
  const errorMessagesMapping = [
39
68
  {
40
69
  regex: /^\[es_rejected_execution_exception] rejected execution .*? on EsThreadPoolExecutor\[(.*?), .*$/,
@@ -258,8 +287,8 @@ class ESWrapper {
258
287
  return kerror.get("unexpected_bad_request", errorMessage);
259
288
  }
260
289
  _handleUnknownError(error, message) {
261
- debug("unhandled elasticsearch error (unhandled type: %s): %o", (0, get_1.default)(error, "error.meta.statusCode", "<no status code>"), error);
262
- return kerror.get("unexpected_error", message);
290
+ debug("unhandled elasticsearch error (unhandled type: %s): %o", (0, get_1.default)(error, "meta.statusCode", "<no status code>"), error);
291
+ return kerror.get("unexpected_error", isUsableMessage(message) ? message : describeUnreadableError(error));
263
292
  }
264
293
  }
265
294
  module.exports = ESWrapper;
@@ -1,6 +1,7 @@
1
1
  import type { KDocument, KDocumentContent } from "kuzzle-types";
2
2
  import type { JSONObject } from "../JSONObject";
3
- import type { KuzzleRequest, PipeEventHandler } from "../../../index";
3
+ import type { KuzzleRequest } from "../../api/request";
4
+ import type { PipeEventHandler } from "../EventHandler";
4
5
  /**
5
6
  * Events with documents only having the `_id`
6
7
  */
@@ -1,5 +1,5 @@
1
1
  import type { JSONObject } from "../JSONObject";
2
- import type { PipeEventHandler } from "../../../index";
2
+ import type { PipeEventHandler } from "../EventHandler";
3
3
  import type { ClientConnection } from "../ClientConnection";
4
4
  import type { HttpMessage } from "../HttpMessage";
5
5
  export type EventHTTPBeforeParsingPayload = {
@@ -101,7 +101,8 @@ export declare class Mutex {
101
101
  *
102
102
  * From then on, `lock()` never resolves: the process is about to exit, and
103
103
  * answering `false` would send its caller down a path meant for a lock
104
- * held by another node.
104
+ * held by another node. And `unlock()` on a lock it already freed resolves
105
+ * without doing anything, since its holder may still try to give it back.
105
106
  */
106
107
  static releaseAllBeforeExit(): Promise<void>;
107
108
  get locked(): boolean;
@@ -165,6 +165,11 @@ class Mutex {
165
165
  * @return {Promise}
166
166
  */
167
167
  async unlock() {
168
+ // `releaseAllBeforeExit` already gave this lock back: its holder is only
169
+ // catching up, and throwing would turn a requested shutdown into a crash.
170
+ if (!this._locked && exiting) {
171
+ return;
172
+ }
168
173
  if (!this._locked) {
169
174
  throw fatal.get("assertion_failed", `tried to unlock the resource "${this.resource}", which is not locked (mutex id: ${this.mutexId})`);
170
175
  }
@@ -214,7 +219,8 @@ class Mutex {
214
219
  *
215
220
  * From then on, `lock()` never resolves: the process is about to exit, and
216
221
  * answering `false` would send its caller down a path meant for a lock
217
- * held by another node.
222
+ * held by another node. And `unlock()` on a lock it already freed resolves
223
+ * without doing anything, since its holder may still try to give it back.
218
224
  */
219
225
  static async releaseAllBeforeExit() {
220
226
  exiting = true;
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kuzzle",
3
3
  "author": "The Kuzzle Team <support@kuzzle.io>",
4
- "version": "2.60.0-beta.2",
4
+ "version": "2.60.0-beta.4",
5
5
  "description": "Kuzzle is an open-source solution that handles all the data management through a secured API, with a large choice of protocols.",
6
6
  "scripts": {
7
7
  "build": "rm -Rf ./dist && tsc && node -r ts-node/register/transpile-only ./bin/copy-binaries.ts",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kuzzle",
3
3
  "author": "The Kuzzle Team <support@kuzzle.io>",
4
- "version": "2.60.0-beta.2",
4
+ "version": "2.60.0-beta.4",
5
5
  "description": "Kuzzle is an open-source solution that handles all the data management through a secured API, with a large choice of protocols.",
6
6
  "scripts": {
7
7
  "build": "rm -Rf ./dist && tsc && node -r ts-node/register/transpile-only ./bin/copy-binaries.ts",