kuzzle 2.56.0 → 2.57.0-beta.2
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/dist/bin/copy-binaries.js +71 -4
- package/dist/bin/start-kuzzle-server +191 -97
- package/dist/index.d.ts +31 -1
- package/dist/index.js +45 -2
- package/dist/lib/api/controllers/adminController.d.ts +4 -3
- package/dist/lib/api/controllers/adminController.js +1 -2
- package/dist/lib/api/controllers/authController.d.ts +37 -16
- package/dist/lib/api/controllers/authController.js +88 -39
- package/dist/lib/api/controllers/baseController.d.ts +10 -5
- package/dist/lib/api/controllers/baseController.js +36 -15
- package/dist/lib/api/controllers/bulkController.d.ts +11 -7
- package/dist/lib/api/controllers/bulkController.js +28 -5
- package/dist/lib/api/controllers/clusterController.d.ts +2 -2
- package/dist/lib/api/controllers/clusterController.js +3 -3
- package/dist/lib/api/controllers/collectionController.d.ts +57 -21
- package/dist/lib/api/controllers/collectionController.js +40 -11
- package/dist/lib/api/controllers/debugController.d.ts +1 -1
- package/dist/lib/api/controllers/documentController.d.ts +137 -28
- package/dist/lib/api/controllers/documentController.js +156 -125
- package/dist/lib/api/controllers/index.d.ts +27 -12
- package/dist/lib/api/controllers/index.js +28 -13
- package/dist/lib/api/controllers/indexController.d.ts +17 -10
- package/dist/lib/api/controllers/indexController.js +13 -11
- package/dist/lib/api/controllers/memoryStorageController.d.ts +2 -5
- package/dist/lib/api/controllers/memoryStorageController.js +199 -127
- package/dist/lib/api/controllers/realtimeController.d.ts +19 -9
- package/dist/lib/api/controllers/realtimeController.js +8 -4
- package/dist/lib/api/controllers/securityController.d.ts +42 -29
- package/dist/lib/api/controllers/securityController.js +35 -19
- package/dist/lib/api/controllers/serverController.d.ts +114 -19
- package/dist/lib/api/controllers/serverController.js +69 -29
- package/dist/lib/api/documentExtractor.d.ts +9 -7
- package/dist/lib/api/documentExtractor.js +105 -41
- package/dist/lib/api/funnel.d.ts +131 -29
- package/dist/lib/api/funnel.js +436 -225
- package/dist/lib/api/httpRoutes.d.ts +16 -11
- package/dist/lib/api/httpRoutes.js +272 -2
- package/dist/lib/api/openapi/OpenApiManager.d.ts +1 -1
- package/dist/lib/api/openapi/openApiGenerator.d.ts +1 -1
- package/dist/lib/api/rateLimiter.d.ts +15 -5
- package/dist/lib/api/rateLimiter.js +42 -14
- package/dist/lib/api/request/kuzzleRequest.d.ts +83 -12
- package/dist/lib/api/request/kuzzleRequest.js +88 -23
- package/dist/lib/api/request/requestContext.d.ts +20 -5
- package/dist/lib/api/request/requestContext.js +14 -4
- package/dist/lib/api/request/requestInput.d.ts +37 -6
- package/dist/lib/api/request/requestInput.js +22 -9
- package/dist/lib/api/request/requestResponse.d.ts +45 -9
- package/dist/lib/api/request/requestResponse.js +34 -3
- package/dist/lib/cluster/command.d.ts +131 -37
- package/dist/lib/cluster/command.js +243 -71
- package/dist/lib/cluster/idCardHandler.d.ts +18 -5
- package/dist/lib/cluster/idCardHandler.js +52 -19
- package/dist/lib/cluster/index.d.ts +8 -2
- package/dist/lib/cluster/index.js +6 -2
- package/dist/lib/cluster/node.d.ts +127 -49
- package/dist/lib/cluster/node.js +412 -179
- package/dist/lib/cluster/protobuf/command.proto +18 -0
- package/dist/lib/cluster/protobuf/commandMessages.d.ts +104 -0
- package/dist/lib/cluster/protobuf/commandMessages.js +30 -0
- package/dist/lib/cluster/protobuf/syncMessages.d.ts +184 -0
- package/dist/lib/cluster/protobuf/syncMessages.js +23 -0
- package/dist/lib/cluster/publisher.d.ts +118 -78
- package/dist/lib/cluster/publisher.js +174 -72
- package/dist/lib/cluster/state.d.ts +15 -9
- package/dist/lib/cluster/state.js +12 -8
- package/dist/lib/cluster/subscriber.d.ts +185 -131
- package/dist/lib/cluster/subscriber.js +357 -95
- package/dist/lib/cluster/workers/IDCardRenewer.d.ts +39 -8
- package/dist/lib/cluster/workers/IDCardRenewer.js +88 -23
- package/dist/lib/config/default.config.d.ts +2 -2
- package/dist/lib/config/default.config.js +10 -3
- package/dist/lib/config/documentEventAliases.d.ts +1 -1
- package/dist/lib/config/index.d.ts +2 -5
- package/dist/lib/config/index.js +30 -17
- package/dist/lib/core/auth/formatProcessing.d.ts +3 -3
- package/dist/lib/core/auth/passportResponse.d.ts +1 -1
- package/dist/lib/core/auth/passportWrapper.d.ts +1 -1
- package/dist/lib/core/auth/passportWrapper.js +14 -13
- package/dist/lib/core/auth/tokenManager.d.ts +24 -3
- package/dist/lib/core/auth/tokenManager.js +47 -19
- package/dist/lib/core/backend/applicationManager.d.ts +2 -2
- package/dist/lib/core/backend/backend.d.ts +16 -5
- package/dist/lib/core/backend/backend.js +14 -6
- package/dist/lib/core/backend/backendCluster.d.ts +1 -1
- package/dist/lib/core/backend/backendConfig.d.ts +17 -5
- package/dist/lib/core/backend/backendConfig.js +1 -0
- package/dist/lib/core/backend/backendController.d.ts +1 -1
- package/dist/lib/core/backend/backendController.js +8 -1
- package/dist/lib/core/backend/backendErrors.d.ts +15 -8
- package/dist/lib/core/backend/backendErrors.js +5 -0
- package/dist/lib/core/backend/backendHook.d.ts +1 -1
- package/dist/lib/core/backend/backendImport.d.ts +1 -1
- package/dist/lib/core/backend/backendOpenApi.d.ts +3 -2
- package/dist/lib/core/backend/backendPipe.d.ts +1 -1
- package/dist/lib/core/backend/backendPipe.js +9 -1
- package/dist/lib/core/backend/backendPlugin.d.ts +2 -2
- package/dist/lib/core/backend/backendSubscription.d.ts +2 -2
- package/dist/lib/core/backend/backendVault.d.ts +1 -1
- package/dist/lib/core/backend/backendVault.js +8 -1
- package/dist/lib/core/cache/cacheEngine.d.ts +7 -4
- package/dist/lib/core/cache/cacheEngine.js +25 -8
- package/dist/lib/core/debug/kuzzleDebugger.d.ts +2 -1
- package/dist/lib/core/debug/kuzzleDebugger.js +31 -17
- package/dist/lib/core/network/accessLogger.d.ts +33 -18
- package/dist/lib/core/network/accessLogger.js +135 -73
- package/dist/lib/core/network/clientConnection.d.ts +13 -11
- package/dist/lib/core/network/clientConnection.js +33 -9
- package/dist/lib/core/network/context.d.ts +17 -22
- package/dist/lib/core/network/context.js +43 -26
- package/dist/lib/core/network/entryPoint.d.ts +38 -16
- package/dist/lib/core/network/entryPoint.js +119 -48
- package/dist/lib/core/network/httpRouter/index.d.ts +20 -50
- package/dist/lib/core/network/httpRouter/index.js +61 -75
- package/dist/lib/core/network/httpRouter/routeHandler.d.ts +17 -32
- package/dist/lib/core/network/httpRouter/routeHandler.js +26 -36
- package/dist/lib/core/network/httpRouter/routePart.d.ts +9 -18
- package/dist/lib/core/network/httpRouter/routePart.js +61 -43
- package/dist/lib/core/network/httpRouter/routeTypes.d.ts +3 -0
- package/dist/lib/core/network/httpRouter/routeTypes.js +23 -0
- package/dist/lib/core/network/networkEntryPoint.d.ts +20 -0
- package/dist/lib/core/network/networkEntryPoint.js +23 -0
- package/dist/lib/core/network/protocolManifest.d.ts +5 -4
- package/dist/lib/core/network/protocolManifest.js +6 -3
- package/dist/lib/core/network/protocols/httpMessage.d.ts +25 -18
- package/dist/lib/core/network/protocols/httpMessage.js +14 -11
- package/dist/lib/core/network/protocols/httpwsProtocol.d.ts +84 -93
- package/dist/lib/core/network/protocols/httpwsProtocol.js +284 -129
- package/dist/lib/core/network/protocols/internalProtocol.d.ts +26 -15
- package/dist/lib/core/network/protocols/internalProtocol.js +17 -16
- package/dist/lib/core/network/protocols/mqttProtocol.d.ts +56 -32
- package/dist/lib/core/network/protocols/mqttProtocol.js +91 -44
- package/dist/lib/core/network/protocols/protocol.d.ts +85 -21
- package/dist/lib/core/network/protocols/protocol.js +83 -27
- package/dist/lib/core/network/router.d.ts +13 -22
- package/dist/lib/core/network/router.js +59 -46
- package/dist/lib/core/plugin/plugin.d.ts +57 -51
- package/dist/lib/core/plugin/plugin.js +218 -103
- package/dist/lib/core/plugin/pluginContext.d.ts +54 -11
- package/dist/lib/core/plugin/pluginContext.js +99 -58
- package/dist/lib/core/plugin/pluginManifest.d.ts +6 -2
- package/dist/lib/core/plugin/pluginManifest.js +18 -11
- package/dist/lib/core/plugin/pluginRepository.d.ts +35 -44
- package/dist/lib/core/plugin/pluginRepository.js +35 -50
- package/dist/lib/core/plugin/pluginsManager.d.ts +128 -46
- package/dist/lib/core/plugin/pluginsManager.js +468 -231
- package/dist/lib/core/plugin/privilegedContext.d.ts +4 -9
- package/dist/lib/core/plugin/privilegedContext.js +5 -10
- package/dist/lib/core/realtime/actionEnum.d.ts +12 -21
- package/dist/lib/core/realtime/actionEnum.js +6 -7
- package/dist/lib/core/realtime/channel.d.ts +11 -5
- package/dist/lib/core/realtime/channel.js +17 -2
- package/dist/lib/core/realtime/connectionRooms.d.ts +2 -2
- package/dist/lib/core/realtime/hotelClerk.d.ts +12 -6
- package/dist/lib/core/realtime/hotelClerk.js +29 -11
- package/dist/lib/core/realtime/index.d.ts +8 -3
- package/dist/lib/core/realtime/index.js +12 -5
- package/dist/lib/core/realtime/notification/document.d.ts +54 -25
- package/dist/lib/core/realtime/notification/document.js +10 -13
- package/dist/lib/core/realtime/notification/index.d.ts +9 -3
- package/dist/lib/core/realtime/notification/index.js +10 -4
- package/dist/lib/core/realtime/notification/server.d.ts +7 -10
- package/dist/lib/core/realtime/notification/server.js +4 -7
- package/dist/lib/core/realtime/notification/user.d.ts +45 -21
- package/dist/lib/core/realtime/notification/user.js +8 -9
- package/dist/lib/core/realtime/notifier.d.ts +52 -40
- package/dist/lib/core/realtime/notifier.js +60 -25
- package/dist/lib/core/realtime/room.d.ts +1 -1
- package/dist/lib/core/realtime/subscription.d.ts +11 -6
- package/dist/lib/core/realtime/subscription.js +5 -1
- package/dist/lib/core/security/index.d.ts +14 -6
- package/dist/lib/core/security/index.js +27 -13
- package/dist/lib/core/security/profileRepository.d.ts +24 -19
- package/dist/lib/core/security/profileRepository.js +23 -10
- package/dist/lib/core/security/roleRepository.d.ts +60 -23
- package/dist/lib/core/security/roleRepository.js +122 -53
- package/dist/lib/core/security/securityLoader.d.ts +33 -18
- package/dist/lib/core/security/securityLoader.js +43 -16
- package/dist/lib/core/security/tokenRepository.d.ts +21 -7
- package/dist/lib/core/security/tokenRepository.js +50 -23
- package/dist/lib/core/security/userRepository.d.ts +44 -21
- package/dist/lib/core/security/userRepository.js +59 -19
- package/dist/lib/core/shared/KoncordeWrapper.d.ts +1 -1
- package/dist/lib/core/shared/ObjectRepository.d.ts +80 -18
- package/dist/lib/core/shared/ObjectRepository.js +57 -29
- package/dist/lib/core/shared/abstractManifest.d.ts +21 -8
- package/dist/lib/core/shared/abstractManifest.js +57 -17
- package/dist/lib/core/shared/sdk/embeddedSdk.d.ts +3 -1
- package/dist/lib/core/shared/sdk/embeddedSdk.js +8 -7
- package/dist/lib/core/shared/sdk/funnelProtocol.d.ts +2 -1
- package/dist/lib/core/shared/sdk/funnelProtocol.js +12 -3
- package/dist/lib/core/shared/sdk/impersonatedSdk.d.ts +11 -5
- package/dist/lib/core/shared/sdk/impersonatedSdk.js +22 -9
- package/dist/lib/core/shared/store.d.ts +45 -29
- package/dist/lib/core/shared/store.js +48 -27
- package/dist/lib/core/statistics/statistics.d.ts +1 -1
- package/dist/lib/core/storage/clientAdapter.d.ts +39 -21
- package/dist/lib/core/storage/clientAdapter.js +88 -48
- package/dist/lib/core/storage/indexCache.d.ts +8 -0
- package/dist/lib/core/storage/indexCache.js +16 -7
- package/dist/lib/core/storage/storageEngine.d.ts +7 -6
- package/dist/lib/core/storage/storageEngine.js +11 -8
- package/dist/lib/core/validation/baseType.d.ts +22 -22
- package/dist/lib/core/validation/baseType.js +11 -26
- package/dist/lib/core/validation/specification.d.ts +148 -0
- package/dist/lib/core/validation/specification.js +34 -0
- package/dist/lib/core/validation/typeOptions.d.ts +62 -0
- package/dist/lib/core/validation/typeOptions.js +23 -0
- package/dist/lib/core/validation/types/anything.d.ts +3 -6
- package/dist/lib/core/validation/types/anything.js +7 -7
- package/dist/lib/core/validation/types/boolean.d.ts +5 -12
- package/dist/lib/core/validation/types/boolean.js +7 -12
- package/dist/lib/core/validation/types/date.d.ts +19 -11
- package/dist/lib/core/validation/types/date.js +212 -161
- package/dist/lib/core/validation/types/email.d.ts +7 -11
- package/dist/lib/core/validation/types/email.js +13 -17
- package/dist/lib/core/validation/types/enum.d.ts +7 -11
- package/dist/lib/core/validation/types/enum.js +39 -20
- package/dist/lib/core/validation/types/geoPoint.d.ts +5 -13
- package/dist/lib/core/validation/types/geoPoint.js +10 -15
- package/dist/lib/core/validation/types/geoShape.d.ts +22 -21
- package/dist/lib/core/validation/types/geoShape.js +113 -129
- package/dist/lib/core/validation/types/geoShapeUtils.d.ts +15 -0
- package/dist/lib/core/validation/types/geoShapeUtils.js +75 -0
- package/dist/lib/core/validation/types/integer.d.ts +7 -5
- package/dist/lib/core/validation/types/integer.js +7 -13
- package/dist/lib/core/validation/types/ipAddress.d.ts +7 -11
- package/dist/lib/core/validation/types/ipAddress.js +13 -17
- package/dist/lib/core/validation/types/numeric.d.ts +7 -11
- package/dist/lib/core/validation/types/numeric.js +22 -29
- package/dist/lib/core/validation/types/object.d.ts +8 -15
- package/dist/lib/core/validation/types/object.js +12 -21
- package/dist/lib/core/validation/types/string.d.ts +7 -11
- package/dist/lib/core/validation/types/string.js +22 -29
- package/dist/lib/core/validation/types/url.d.ts +7 -11
- package/dist/lib/core/validation/types/url.js +14 -18
- package/dist/lib/core/validation/validation.d.ts +119 -80
- package/dist/lib/core/validation/validation.js +470 -412
- package/dist/lib/core/validation/validationUtils.d.ts +31 -0
- package/dist/lib/core/validation/validationUtils.js +190 -0
- package/dist/lib/kerror/codes/4-plugin.json +0 -12
- package/dist/lib/kerror/codes/index.d.ts +12 -2
- package/dist/lib/kerror/codes/index.js +5 -4
- package/dist/lib/kerror/errors/badRequestError.d.ts +1 -1
- package/dist/lib/kerror/errors/badRequestError.js +1 -1
- package/dist/lib/kerror/errors/externalServiceError.d.ts +1 -1
- package/dist/lib/kerror/errors/externalServiceError.js +1 -1
- package/dist/lib/kerror/errors/forbiddenError.d.ts +1 -1
- package/dist/lib/kerror/errors/forbiddenError.js +1 -1
- package/dist/lib/kerror/errors/gatewayTimeoutError.d.ts +1 -1
- package/dist/lib/kerror/errors/gatewayTimeoutError.js +1 -1
- package/dist/lib/kerror/errors/internalError.d.ts +1 -1
- package/dist/lib/kerror/errors/internalError.js +1 -1
- package/dist/lib/kerror/errors/kuzzleError.d.ts +32 -3
- package/dist/lib/kerror/errors/kuzzleError.js +21 -7
- package/dist/lib/kerror/errors/multipleErrorsError.d.ts +2 -2
- package/dist/lib/kerror/errors/multipleErrorsError.js +2 -2
- package/dist/lib/kerror/errors/notFoundError.d.ts +1 -1
- package/dist/lib/kerror/errors/notFoundError.js +1 -1
- package/dist/lib/kerror/errors/partialError.d.ts +8 -2
- package/dist/lib/kerror/errors/partialError.js +22 -9
- package/dist/lib/kerror/errors/pluginImplementationError.d.ts +9 -1
- package/dist/lib/kerror/errors/pluginImplementationError.js +9 -3
- package/dist/lib/kerror/errors/preconditionError.d.ts +1 -1
- package/dist/lib/kerror/errors/preconditionError.js +1 -1
- package/dist/lib/kerror/errors/serviceUnavailableError.d.ts +1 -1
- package/dist/lib/kerror/errors/serviceUnavailableError.js +1 -1
- package/dist/lib/kerror/errors/sizeLimitError.d.ts +1 -1
- package/dist/lib/kerror/errors/sizeLimitError.js +1 -1
- package/dist/lib/kerror/errors/tooManyRequestsError.d.ts +1 -1
- package/dist/lib/kerror/errors/tooManyRequestsError.js +1 -1
- package/dist/lib/kerror/errors/unauthorizedError.d.ts +1 -1
- package/dist/lib/kerror/errors/unauthorizedError.js +1 -1
- package/dist/lib/kerror/index.d.ts +19 -13
- package/dist/lib/kerror/index.js +60 -19
- package/dist/lib/kuzzle/Logger.d.ts +2 -2
- package/dist/lib/kuzzle/dumpGenerator.d.ts +12 -8
- package/dist/lib/kuzzle/dumpGenerator.js +130 -62
- package/dist/lib/kuzzle/event/KuzzleEventEmitter.d.ts +18 -9
- package/dist/lib/kuzzle/event/KuzzleEventEmitter.js +45 -18
- package/dist/lib/kuzzle/event/pipeRunner.d.ts +37 -16
- package/dist/lib/kuzzle/event/pipeRunner.js +73 -26
- package/dist/lib/kuzzle/event/waterfall.d.ts +11 -1
- package/dist/lib/kuzzle/event/waterfall.js +38 -13
- package/dist/lib/kuzzle/internalIndexHandler.d.ts +11 -174
- package/dist/lib/kuzzle/internalIndexHandler.js +58 -29
- package/dist/lib/kuzzle/kuzzle.d.ts +25 -10
- package/dist/lib/kuzzle/kuzzle.js +74 -17
- package/dist/lib/kuzzle/kuzzleStateEnum.d.ts +16 -12
- package/dist/lib/kuzzle/kuzzleStateEnum.js +5 -7
- package/dist/lib/kuzzle/vault.d.ts +5 -1
- package/dist/lib/kuzzle/vault.js +29 -17
- package/dist/lib/model/security/profile.d.ts +34 -5
- package/dist/lib/model/security/profile.js +6 -5
- package/dist/lib/model/security/rights.d.ts +12 -4
- package/dist/lib/model/security/rights.js +7 -6
- package/dist/lib/model/security/role.d.ts +8 -4
- package/dist/lib/model/security/role.js +12 -6
- package/dist/lib/model/security/token.d.ts +8 -0
- package/dist/lib/model/security/user.d.ts +8 -2
- package/dist/lib/model/security/user.js +7 -4
- package/dist/lib/model/storage/apiKey.d.ts +55 -26
- package/dist/lib/model/storage/apiKey.js +97 -24
- package/dist/lib/model/storage/baseModel.d.ts +77 -66
- package/dist/lib/model/storage/baseModel.js +21 -34
- package/dist/lib/service/cache/redis.d.ts +53 -36
- package/dist/lib/service/cache/redis.js +148 -71
- package/dist/lib/service/service.d.ts +14 -19
- package/dist/lib/service/service.js +41 -22
- package/dist/lib/service/storage/7/elasticsearch.d.ts +136 -93
- package/dist/lib/service/storage/7/elasticsearch.js +218 -165
- package/dist/lib/service/storage/7/esWrapper.d.ts +30 -13
- package/dist/lib/service/storage/7/esWrapper.js +58 -37
- package/dist/lib/service/storage/8/elasticsearch.d.ts +153 -119
- package/dist/lib/service/storage/8/elasticsearch.js +286 -213
- package/dist/lib/service/storage/8/esWrapper.d.ts +30 -13
- package/dist/lib/service/storage/8/esWrapper.js +59 -38
- package/dist/lib/service/storage/commons/queryTranslator.d.ts +3 -3
- package/dist/lib/service/storage/commons/queryTranslator.js +71 -35
- package/dist/lib/types/ApiRoute.d.ts +12 -0
- package/dist/lib/types/ApiRoute.js +23 -0
- package/dist/lib/types/ClientConnection.d.ts +1 -1
- package/dist/lib/types/EventHandler.d.ts +10 -0
- package/dist/lib/types/Global.d.ts +8 -3
- package/dist/lib/types/HttpMessage.d.ts +2 -2
- package/dist/lib/types/HttpStream.d.ts +1 -1
- package/dist/lib/types/HttpStream.js +3 -1
- package/dist/lib/types/JSONObject.d.ts +9 -0
- package/dist/lib/types/JSONObject.js +23 -0
- package/dist/lib/types/Kuzzle.d.ts +5 -3
- package/dist/lib/types/KuzzleDocument.d.ts +1 -1
- package/dist/lib/types/KuzzleWebSocket.d.ts +24 -0
- package/dist/lib/types/KuzzleWebSocket.js +23 -0
- package/dist/lib/types/OpenApiDefinition.d.ts +1 -1
- package/dist/lib/types/Plugin.d.ts +33 -8
- package/dist/lib/types/PluginInstance.d.ts +67 -0
- package/dist/lib/types/PluginInstance.js +23 -0
- package/dist/lib/types/Policy.d.ts +1 -1
- package/dist/lib/types/Target.d.ts +8 -0
- package/dist/lib/types/Token.d.ts +14 -2
- package/dist/lib/types/config/HttpConfiguration.d.ts +5 -0
- package/dist/lib/types/config/KuzzleConfiguration.d.ts +102 -1
- package/dist/lib/types/config/PluginsConfiguration.d.ts +3 -2
- package/dist/lib/types/config/SecurityConfiguration.d.ts +6 -2
- package/dist/lib/types/config/ServerConfiguration.d.ts +2 -2
- package/dist/lib/types/config/ServicesConfiguration.d.ts +1 -1
- package/dist/lib/types/config/cache/BaseCacheRedisConfiguration.d.ts +131 -0
- package/dist/lib/types/config/cache/BaseCacheRedisConfiguration.js +3 -0
- package/dist/lib/types/config/internalCache/InternalCacheRedisConfiguration.d.ts +2 -106
- package/dist/lib/types/config/publicCache/PublicCacheRedisConfiguration.d.ts +2 -26
- package/dist/lib/types/config/storageEngine/StorageEngineElasticsearchConfiguration.d.ts +3 -0
- package/dist/lib/types/controllers/Controller.d.ts +12 -3
- package/dist/lib/types/controllers/ControllerDefinition.d.ts +2 -2
- package/dist/lib/types/controllers/{adminControlller.type.js → adminController.type.js} +1 -1
- package/dist/lib/types/controllers/authController.type.d.ts +2 -2
- package/dist/lib/types/core/auth/formatProcessing.type.d.ts +7 -1
- package/dist/lib/types/errors/ErrorDomains.d.ts +1 -1
- package/dist/lib/types/events/EventGenericDocument.d.ts +10 -9
- package/dist/lib/types/events/EventProtocol.d.ts +4 -4
- package/dist/lib/types/index.d.ts +7 -0
- package/dist/lib/types/index.js +7 -0
- package/dist/lib/types/shared/StoreCollectionsDefinition.d.ts +1 -1
- package/dist/lib/types/storage/7/Elasticsearch.d.ts +62 -13
- package/dist/lib/types/storage/8/Elasticsearch.d.ts +62 -13
- package/dist/lib/util/Inflector.d.ts +4 -1
- package/dist/lib/util/Inflector.js +0 -3
- package/dist/lib/util/assertType.d.ts +24 -27
- package/dist/lib/util/assertType.js +28 -39
- package/dist/lib/util/async.js +19 -6
- package/dist/lib/util/asyncStore.js +1 -1
- package/dist/lib/util/bufferedPassThrough.d.ts +9 -0
- package/dist/lib/util/bufferedPassThrough.js +27 -13
- package/dist/lib/util/bytes.d.ts +1 -1
- package/dist/lib/util/bytes.js +6 -2
- package/dist/lib/util/debug.d.ts +3 -1
- package/dist/lib/util/debug.js +42 -10
- package/dist/lib/util/deprecate.d.ts +9 -1
- package/dist/lib/util/deprecate.js +10 -6
- package/dist/lib/util/didYouMean.d.ts +2 -1
- package/dist/lib/util/didYouMean.js +8 -4
- package/dist/lib/util/distributedLock.d.ts +56 -0
- package/dist/lib/util/distributedLock.js +166 -0
- package/dist/lib/util/dump-collection.d.ts +1 -1
- package/dist/lib/util/dump-collection.js +4 -5
- package/dist/lib/util/extractFields.d.ts +15 -11
- package/dist/lib/util/extractFields.js +8 -9
- package/dist/lib/util/koncordeCompat.d.ts +2 -2
- package/dist/lib/util/koncordeCompat.js +33 -0
- package/dist/lib/util/memoize.d.ts +7 -6
- package/dist/lib/util/memoize.js +3 -3
- package/dist/lib/util/mutex.d.ts +22 -2
- package/dist/lib/util/mutex.js +39 -1
- package/dist/lib/util/name-generator.js +14 -2
- package/dist/lib/util/promback.d.ts +20 -11
- package/dist/lib/util/promback.js +14 -7
- package/dist/lib/util/requestAssertions.d.ts +30 -10
- package/dist/lib/util/requestAssertions.js +125 -121
- package/dist/lib/util/safeObject.d.ts +3 -3
- package/dist/lib/util/safeObject.js +7 -3
- package/dist/lib/util/stackTrace.d.ts +13 -5
- package/dist/lib/util/stackTrace.js +17 -12
- package/dist/lib/util/thrown.d.ts +13 -0
- package/dist/lib/util/thrown.js +46 -0
- package/dist/package.json +26 -19
- package/package.json +26 -19
- package/dist/lib/util/wildcard.d.ts +0 -1
- package/dist/lib/util/wildcard.js +0 -46
- /package/dist/lib/types/controllers/{adminControlller.type.d.ts → adminController.type.d.ts} +0 -0
package/dist/lib/cluster/node.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
* Kuzzle, a backend software, self-hostable and ready to use
|
|
3
4
|
* to power modern apps
|
|
@@ -18,22 +19,51 @@
|
|
|
18
19
|
* See the License for the specific language governing permissions and
|
|
19
20
|
* limitations under the License.
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
// Bare specifiers, not the `node:` prefix: `node.test.js` intercepts `os` with
|
|
26
|
+
// `mock-require` to feed `getIP()` a fixed set of network interfaces, and a
|
|
27
|
+
// `node:os` import compiles to `require("node:os")`, which that interception
|
|
28
|
+
// does not see. A conversion must not change how a module is resolved — see
|
|
29
|
+
// J3a, where a hard-coded `.js` next to a renamed file took the whole
|
|
30
|
+
// functional matrix down.
|
|
31
|
+
const assert_1 = __importDefault(require("assert")); // NOSONAR
|
|
32
|
+
const util_1 = require("util"); // NOSONAR
|
|
33
|
+
const net_1 = __importDefault(require("net")); // NOSONAR
|
|
34
|
+
const os_1 = __importDefault(require("os")); // NOSONAR
|
|
35
|
+
const bluebird_1 = __importDefault(require("bluebird"));
|
|
36
|
+
const eventemitter3_1 = __importDefault(require("eventemitter3"));
|
|
37
|
+
const intersection_1 = __importDefault(require("lodash/intersection"));
|
|
38
|
+
const xor_1 = __importDefault(require("lodash/xor"));
|
|
39
|
+
const long_1 = __importDefault(require("long"));
|
|
40
|
+
const kuzzleStateEnum_1 = __importDefault(require("../kuzzle/kuzzleStateEnum"));
|
|
41
|
+
const debug_1 = __importDefault(require("../util/debug"));
|
|
42
|
+
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
|
|
47
|
+
const command_1 = __importDefault(require("./command"));
|
|
48
|
+
const idCardHandler_1 = require("./idCardHandler");
|
|
49
|
+
const publisher_1 = __importDefault(require("./publisher"));
|
|
50
|
+
const state_1 = __importDefault(require("./state"));
|
|
51
|
+
const subscriber_1 = __importDefault(require("./subscriber"));
|
|
52
|
+
const debug = (0, debug_1.default)("kuzzle:cluster:sync");
|
|
53
|
+
/**
|
|
54
|
+
* How long `addNode()` may wait for a joining node's first sync message before
|
|
55
|
+
* it answers that node's handshake anyway.
|
|
56
|
+
*
|
|
57
|
+
* `addNode()` runs inside the handshake command handler, and the joining node
|
|
58
|
+
* stops waiting for the answer after 2000 ms — hard-coded in
|
|
59
|
+
* `ClusterCommand._sendSingleHandshake`, and in every release before it, so a
|
|
60
|
+
* node joining during a rolling upgrade has the same limit. Waiting up to
|
|
61
|
+
* `2 × cluster.heartbeat` (4 s by default) there made the joiner give up on a
|
|
62
|
+
* node that had already added it: a one-sided membership (step 15, F-03).
|
|
63
|
+
* Half the joiner's limit leaves the rest for the Redis round trip and the
|
|
64
|
+
* command socket.
|
|
65
|
+
*/
|
|
66
|
+
const HANDSHAKE_PROOF_BUDGET = 1000;
|
|
37
67
|
/**
|
|
38
68
|
* Test an IP address and determine if it's in the public or private range.
|
|
39
69
|
*
|
|
@@ -41,15 +71,19 @@ const { fromKoncordeIndex } = require("../util/koncordeCompat");
|
|
|
41
71
|
* @return {Boolean}
|
|
42
72
|
*/
|
|
43
73
|
function isPrivateIP(ip) {
|
|
44
|
-
if (
|
|
45
|
-
|
|
74
|
+
if (net_1.default.isIPv6(ip)) {
|
|
75
|
+
// `split` always yields a first element, empty string included.
|
|
76
|
+
const prefix = ip.split(":")[0] ?? "";
|
|
46
77
|
return ((prefix.startsWith("fd") && prefix.length === 4) || prefix === "fe80");
|
|
47
78
|
}
|
|
48
|
-
// IPv4
|
|
79
|
+
// IPv4. `?? Number.NaN` on the octets a malformed address does not have: every
|
|
80
|
+
// comparison below is then false, which is what comparing `undefined` did.
|
|
49
81
|
const exploded = ip.split(".").map((s) => Number.parseInt(s));
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
82
|
+
const first = exploded[0] ?? Number.NaN;
|
|
83
|
+
const second = exploded[1] ?? Number.NaN;
|
|
84
|
+
return (first === 10 ||
|
|
85
|
+
(first === 172 && second >= 16 && second <= 31) ||
|
|
86
|
+
(first === 192 && second === 168));
|
|
53
87
|
}
|
|
54
88
|
/**
|
|
55
89
|
* Some IPv4 addresses are reserved for internal uses only, and cannot be
|
|
@@ -60,18 +94,20 @@ function isPrivateIP(ip) {
|
|
|
60
94
|
function isInternalIP(ip) {
|
|
61
95
|
// To my knowledge, there aren't any reserved, non-loopback and non-routable
|
|
62
96
|
// IPv6 addresses
|
|
63
|
-
if (
|
|
97
|
+
if (net_1.default.isIPv6(ip)) {
|
|
64
98
|
return false;
|
|
65
99
|
}
|
|
66
100
|
const exploded = ip.split(".").map((s) => Number.parseInt(s));
|
|
101
|
+
const first = exploded[0] ?? Number.NaN;
|
|
102
|
+
const second = exploded[1] ?? Number.NaN;
|
|
67
103
|
// 127.x.x: loopback addresses are already flagged as "internal" by
|
|
68
104
|
// os.networkInterfaces.
|
|
69
|
-
return (
|
|
105
|
+
return (first === 127 ||
|
|
70
106
|
// 169.254.x.x addresses are APIPA addresses: temporary and non-routable.
|
|
71
107
|
// We need to remove them from the accepted list of IP addresses
|
|
72
108
|
// (this is a "just in case" scenario: APIPA addresses are obsolete and
|
|
73
109
|
// should not be used anymore, but we never know...)
|
|
74
|
-
(
|
|
110
|
+
(first === 169 && second === 254));
|
|
75
111
|
}
|
|
76
112
|
/**
|
|
77
113
|
* Return the first IP address matching the provided configuration
|
|
@@ -81,11 +117,12 @@ function isInternalIP(ip) {
|
|
|
81
117
|
* @param {String} [options.ip] Used to target public or private addresses
|
|
82
118
|
* @return {String|null}
|
|
83
119
|
*/
|
|
84
|
-
function getIP({ family = "IPv4", interface: netInterface, ip } = {}) {
|
|
120
|
+
function getIP({ family = "IPv4", interface: netInterface, ip, } = {}) {
|
|
85
121
|
const mustBePrivate = ip === "private";
|
|
86
122
|
let interfaces = [];
|
|
87
|
-
for (const [key, value] of Object.entries(
|
|
88
|
-
|
|
123
|
+
for (const [key, value] of Object.entries(os_1.default.networkInterfaces())) {
|
|
124
|
+
// `os.networkInterfaces()` declares its values optional.
|
|
125
|
+
for (const _interface of value ?? []) {
|
|
89
126
|
interfaces.push({
|
|
90
127
|
interface: key,
|
|
91
128
|
..._interface,
|
|
@@ -100,12 +137,13 @@ function getIP({ family = "IPv4", interface: netInterface, ip } = {}) {
|
|
|
100
137
|
(!ip || mustBePrivate === isPrivateIP(n.address)));
|
|
101
138
|
});
|
|
102
139
|
debug("Filtered interfaces %o", interfaces);
|
|
103
|
-
|
|
140
|
+
const [firstInterface] = interfaces;
|
|
141
|
+
if (firstInterface === undefined) {
|
|
104
142
|
return null;
|
|
105
143
|
}
|
|
106
144
|
// take the first IP from the list if no interface has been defined
|
|
107
145
|
if (!netInterface) {
|
|
108
|
-
return
|
|
146
|
+
return firstInterface.address;
|
|
109
147
|
}
|
|
110
148
|
for (const i of interfaces) {
|
|
111
149
|
if ([i.interface, i.address, i.mac].includes(netInterface)) {
|
|
@@ -114,9 +152,7 @@ function getIP({ family = "IPv4", interface: netInterface, ip } = {}) {
|
|
|
114
152
|
}
|
|
115
153
|
return null;
|
|
116
154
|
}
|
|
117
|
-
/**
|
|
118
|
-
* @typedef {nodeActivityEnum}
|
|
119
|
-
*/
|
|
155
|
+
/** What `trackActivity` records: a node joined, or a node was evicted. */
|
|
120
156
|
const nodeActivityEnum = Object.freeze({
|
|
121
157
|
ADDED: 1,
|
|
122
158
|
EVICTED: 2,
|
|
@@ -124,34 +160,43 @@ const nodeActivityEnum = Object.freeze({
|
|
|
124
160
|
// Handles the node logic: discovery, eviction, heartbeat, ...
|
|
125
161
|
// Dependencies: core:cache module must be started
|
|
126
162
|
class ClusterNode {
|
|
163
|
+
/**
|
|
164
|
+
* The id of the ID card this node reserved during `handshake()`.
|
|
165
|
+
*
|
|
166
|
+
* Null until the handshake gets that far, and read as a `string` at
|
|
167
|
+
* seventeen sites here plus `subscriber.ts` and `command.ts` — all of which
|
|
168
|
+
* run after it. The accessor says once where that can fail.
|
|
169
|
+
*/
|
|
170
|
+
get nodeId() {
|
|
171
|
+
if (this._nodeId === null) {
|
|
172
|
+
throw new Error("[CLUSTER] No node id yet: the handshake has not run");
|
|
173
|
+
}
|
|
174
|
+
return this._nodeId;
|
|
175
|
+
}
|
|
176
|
+
set nodeId(id) {
|
|
177
|
+
this._nodeId = id;
|
|
178
|
+
}
|
|
127
179
|
constructor() {
|
|
180
|
+
this._nodeId = null;
|
|
128
181
|
this.config = global.kuzzle.config.cluster;
|
|
129
182
|
this.logger = global.kuzzle.log.child("cluster:node");
|
|
130
183
|
this.heartbeatDelay = this.config.heartbeat;
|
|
131
184
|
const family = this.config.ipv6 ? "IPv6" : "IPv4";
|
|
132
|
-
|
|
185
|
+
const ip = getIP({
|
|
133
186
|
family,
|
|
134
187
|
interface: this.config.interface,
|
|
135
188
|
ip: this.config.ip,
|
|
136
189
|
});
|
|
137
|
-
debug("Found IP address: %s with config %o",
|
|
138
|
-
|
|
139
|
-
this.
|
|
190
|
+
debug("Found IP address: %s with config %o", ip, this.config);
|
|
191
|
+
(0, assert_1.default)(ip !== null, `[CLUSTER] No suitable IP address found with the provided configuration (family: ${family}, interface: ${this.config.interface}, ip: ${this.config.ip})`);
|
|
192
|
+
this.ip = ip;
|
|
140
193
|
this.heartbeatTimer = null;
|
|
141
|
-
this.idCardHandler = new ClusterIdCardHandler(this);
|
|
142
|
-
this.publisher = new
|
|
143
|
-
this.fullState = new
|
|
144
|
-
this.command = new
|
|
145
|
-
this.eventEmitter = new
|
|
146
|
-
/**
|
|
147
|
-
* Links remote node IDs with their subscriber counterpart
|
|
148
|
-
* @type {Map.<string, ClusterSubscriber>}
|
|
149
|
-
*/
|
|
194
|
+
this.idCardHandler = new idCardHandler_1.ClusterIdCardHandler(this);
|
|
195
|
+
this.publisher = new publisher_1.default(this);
|
|
196
|
+
this.fullState = new state_1.default();
|
|
197
|
+
this.command = new command_1.default(this);
|
|
198
|
+
this.eventEmitter = new eventemitter3_1.default();
|
|
150
199
|
this.remoteNodes = new Map();
|
|
151
|
-
/**
|
|
152
|
-
* Cluster nodes activity, used to keep track of nodes being added or
|
|
153
|
-
* removed, to give more insights to cluster statuses
|
|
154
|
-
*/
|
|
155
200
|
this.activityMaxLength = this.config.activityDepth;
|
|
156
201
|
this.activity = [];
|
|
157
202
|
}
|
|
@@ -174,7 +219,7 @@ class ClusterNode {
|
|
|
174
219
|
if (this.countActiveNodes() < this.config.minimumNodes) {
|
|
175
220
|
this.logger.info("[CLUSTER] Not enough nodes active. Waiting for other nodes to join the cluster...");
|
|
176
221
|
while (this.countActiveNodes() < this.config.minimumNodes) {
|
|
177
|
-
await
|
|
222
|
+
await bluebird_1.default.delay(100);
|
|
178
223
|
}
|
|
179
224
|
}
|
|
180
225
|
return this.nodeId;
|
|
@@ -184,12 +229,18 @@ class ClusterNode {
|
|
|
184
229
|
* nodes, and removes entries from the cache
|
|
185
230
|
*/
|
|
186
231
|
async shutdown() {
|
|
187
|
-
clearInterval(this.heartbeatTimer);
|
|
232
|
+
clearInterval(this.heartbeatTimer ?? undefined);
|
|
188
233
|
await this.idCardHandler.dispose();
|
|
189
234
|
for (const subscriber of this.remoteNodes.values()) {
|
|
190
235
|
subscriber.dispose();
|
|
191
236
|
}
|
|
192
|
-
|
|
237
|
+
// `_nodeId`, not the accessor, which throws before the handshake: a SIGTERM
|
|
238
|
+
// during cluster init must still reach the two disposals below, as it did
|
|
239
|
+
// on v2.56.0. No peer knows this node before it has an id, so there is
|
|
240
|
+
// nobody to announce the shutdown to.
|
|
241
|
+
if (this._nodeId !== null) {
|
|
242
|
+
this.publisher.sendNodeShutdown(this._nodeId);
|
|
243
|
+
}
|
|
193
244
|
await this.publisher.dispose();
|
|
194
245
|
this.command.dispose();
|
|
195
246
|
}
|
|
@@ -205,9 +256,7 @@ class ClusterNode {
|
|
|
205
256
|
// when debugging.
|
|
206
257
|
// Otherwise when recovering from a debug session, all the other nodes will be evicted.
|
|
207
258
|
for (const subscriber of this.remoteNodes.values()) {
|
|
208
|
-
subscriber.
|
|
209
|
-
evictionPrevented,
|
|
210
|
-
});
|
|
259
|
+
subscriber.setEvictionPrevented(evictionPrevented);
|
|
211
260
|
}
|
|
212
261
|
}
|
|
213
262
|
/**
|
|
@@ -221,16 +270,37 @@ class ClusterNode {
|
|
|
221
270
|
if (this.remoteNodes.has(id)) {
|
|
222
271
|
return false;
|
|
223
272
|
}
|
|
224
|
-
const subscriber = new
|
|
273
|
+
const subscriber = new subscriber_1.default(this, id, ip);
|
|
225
274
|
this.remoteNodes.set(id, subscriber);
|
|
226
275
|
await subscriber.init();
|
|
276
|
+
// Same ordering `handshake()` needs, and for the same reason: `lastMessageId`
|
|
277
|
+
// reached this node over the *command* channel, in the joining node's
|
|
278
|
+
// handshake, while this subscription lives on the *sync* channel. Until the
|
|
279
|
+
// subscription takes effect the remote's PUB socket drops what it publishes,
|
|
280
|
+
// and `sync()` below would resume from just before the dropped message.
|
|
281
|
+
// TD-65 (#2773).
|
|
282
|
+
//
|
|
283
|
+
// What the wait protects is the *answer*: the joining node publishes
|
|
284
|
+
// nothing but heartbeats until every node has answered its handshake, so
|
|
285
|
+
// answering once the subscription is live keeps its first real messages
|
|
286
|
+
// from being dropped. It is bounded well below the joiner's handshake
|
|
287
|
+
// timeout (see HANDSHAKE_PROOF_BUDGET): the subscription itself is live
|
|
288
|
+
// within milliseconds, and what takes up to a heartbeat round is only the
|
|
289
|
+
// joiner's next heartbeat, the proof. Missing the proof is then routine
|
|
290
|
+
// rather than a warning, and a message lost anyway is a gap, which is
|
|
291
|
+
// retransmitted (#2785).
|
|
292
|
+
const proofTimeout = Math.min(this.heartbeatDelay * 2, HANDSHAKE_PROOF_BUDGET);
|
|
293
|
+
const live = await subscriber.waitForSubscription(proofTimeout);
|
|
294
|
+
if (!live) {
|
|
295
|
+
debug("[CLUSTER] No sync message received from node %s within %dms: answering its handshake with a subscription not proven live yet", id, proofTimeout);
|
|
296
|
+
}
|
|
227
297
|
await subscriber.sync(lastMessageId);
|
|
228
298
|
await this.idCardHandler.addNode(id);
|
|
229
299
|
this.logger.info(`[CLUSTER] Node "${id}" joined the cluster`);
|
|
230
300
|
this.trackActivity(id, ip, nodeActivityEnum.ADDED);
|
|
231
|
-
if (global.kuzzle.state ===
|
|
301
|
+
if (global.kuzzle.state === kuzzleStateEnum_1.default.NOT_ENOUGH_NODES &&
|
|
232
302
|
this.countActiveNodes() >= this.config.minimumNodes) {
|
|
233
|
-
global.kuzzle.state =
|
|
303
|
+
global.kuzzle.state = kuzzleStateEnum_1.default.RUNNING;
|
|
234
304
|
this.logger.warn(`[CLUSTER] Minimum number of nodes reached (${this.countActiveNodes()}). This node is now accepting requests again.`);
|
|
235
305
|
}
|
|
236
306
|
return true;
|
|
@@ -247,7 +317,22 @@ class ClusterNode {
|
|
|
247
317
|
if (error) {
|
|
248
318
|
this.logger.error(error.stack);
|
|
249
319
|
}
|
|
250
|
-
|
|
320
|
+
// Same guard as `shutdown()`: before the handshake there is no id to
|
|
321
|
+
// broadcast, and throwing here would skip the shutdown below.
|
|
322
|
+
if (this._nodeId !== null) {
|
|
323
|
+
this.publisher.sendNodeEvicted(this._nodeId, this._nodeId, reason);
|
|
324
|
+
}
|
|
325
|
+
// The broadcast above tells the *other* nodes to forget this one. It cannot
|
|
326
|
+
// tell this one: a ZeroMQ PUB socket does not deliver to its own process, so
|
|
327
|
+
// `ClusterSubscriber.handleNodeEviction`'s `message.nodeId === localNode
|
|
328
|
+
// .nodeId` branch — the one that shuts the node down — is unreachable by the
|
|
329
|
+
// only node that needs it. Without this call the node leaves the cluster and
|
|
330
|
+
// keeps answering requests behind the load balancer, from state that has
|
|
331
|
+
// stopped advancing. See TD-67 (#2776).
|
|
332
|
+
//
|
|
333
|
+
// Exit code 1: this is a failure, and a node that exits 0 is not restarted
|
|
334
|
+
// by an `on-failure` policy (#2785).
|
|
335
|
+
global.kuzzle.shutdown(1);
|
|
251
336
|
}
|
|
252
337
|
/**
|
|
253
338
|
* Evicts a remote from the list
|
|
@@ -256,7 +341,7 @@ class ClusterNode {
|
|
|
256
341
|
* @param {boolean} [options.broadcast] - broadcast the eviction to the cluster
|
|
257
342
|
* @param {string} [options.reason] - reason of eviction
|
|
258
343
|
*/
|
|
259
|
-
async evictNode(nodeId, { broadcast = false, reason = "" }) {
|
|
344
|
+
async evictNode(nodeId, { broadcast = false, reason = "", }) {
|
|
260
345
|
const subscriber = this.remoteNodes.get(nodeId);
|
|
261
346
|
if (!subscriber) {
|
|
262
347
|
this.logger.warn(`[CLUSTER] Node "${nodeId}" with no subscriber evicted. Reason: ${reason}`);
|
|
@@ -272,7 +357,7 @@ class ClusterNode {
|
|
|
272
357
|
this.publisher.sendNodeEvicted(this.nodeId, nodeId, reason);
|
|
273
358
|
}
|
|
274
359
|
if (this.countActiveNodes() < this.config.minimumNodes) {
|
|
275
|
-
global.kuzzle.state =
|
|
360
|
+
global.kuzzle.state = kuzzleStateEnum_1.default.NOT_ENOUGH_NODES;
|
|
276
361
|
this.logger.warn(`[CLUSTER] Not enough nodes active (expected: ${this.config.minimumNodes}, active: ${this.countActiveNodes()}). Deactivating node until new ones are added.`);
|
|
277
362
|
}
|
|
278
363
|
}
|
|
@@ -286,80 +371,21 @@ class ClusterNode {
|
|
|
286
371
|
async enforceClusterConsistency() {
|
|
287
372
|
// Delay the check to 1 heartbeat round, to allow all nodes to update
|
|
288
373
|
// their ID cards
|
|
289
|
-
await
|
|
374
|
+
await bluebird_1.default.delay(this.heartbeatDelay);
|
|
290
375
|
try {
|
|
291
376
|
const idCards = await this.idCardHandler.getRemoteIdCards();
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (topology.length !== idCards.length) {
|
|
298
|
-
topology = topology.sort();
|
|
299
|
-
const found = splits.some((split) => {
|
|
300
|
-
if (split.length !== topology.length) {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
return split.every((id, index) => id === topology[index]);
|
|
304
|
-
});
|
|
305
|
-
if (!found) {
|
|
306
|
-
splits.push(topology);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
377
|
+
const ownIdCard = this.idCardHandler.idCard;
|
|
378
|
+
// A node with no ID card contributes no topology. Pushing `null` is
|
|
379
|
+
// what `detectSplits` then read `.topology` off, one line later.
|
|
380
|
+
if (ownIdCard !== null) {
|
|
381
|
+
idCards.push(ownIdCard);
|
|
309
382
|
}
|
|
383
|
+
const splits = this.detectSplits(idCards);
|
|
310
384
|
// No split detected, the cluster is consistent
|
|
311
385
|
if (splits.length === 0) {
|
|
312
386
|
return;
|
|
313
387
|
}
|
|
314
|
-
|
|
315
|
-
//
|
|
316
|
-
// First we elect the smallest split possible.
|
|
317
|
-
// If multiple splits are eligibles, we choose amongst them the split
|
|
318
|
-
// containing the youngest isolated node (isolated = the node is in this
|
|
319
|
-
// split alone, and in no other splits). If no split is eligible using
|
|
320
|
-
// this method, we fall back to the smallest split containing the youngest
|
|
321
|
-
// node (isolated or not).
|
|
322
|
-
//
|
|
323
|
-
// The goal of this process is to force at least 1 node to kill itself,
|
|
324
|
-
// with all nodes concluding on their own on the same list of nodes to
|
|
325
|
-
// shut down.
|
|
326
|
-
// First remove every non existing node from topologies
|
|
327
|
-
splits = splits.map((topology) => topology.filter((nodeId) => idCards.find((card) => card.id === nodeId)));
|
|
328
|
-
splits = splits.sort((a, b) => a.length - b.length);
|
|
329
|
-
const eligibleSplits = splits.filter((split) => split.length === splits[0].length);
|
|
330
|
-
let candidates;
|
|
331
|
-
if (eligibleSplits.length === 1) {
|
|
332
|
-
candidates = eligibleSplits[0];
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
// Beware: search isolated nodes in ALL the splits, not only the
|
|
336
|
-
// smallest ones
|
|
337
|
-
let isolatedNodes = _.xor(...splits);
|
|
338
|
-
const eligibleNodes = _.uniq(_.flatten(eligibleSplits));
|
|
339
|
-
isolatedNodes = _.intersection(isolatedNodes, eligibleNodes);
|
|
340
|
-
const isIsolated = isolatedNodes.length > 0;
|
|
341
|
-
// safety measure: this should never happen
|
|
342
|
-
if (isolatedNodes.length === 0) {
|
|
343
|
-
isolatedNodes = eligibleNodes;
|
|
344
|
-
}
|
|
345
|
-
let youngestNode;
|
|
346
|
-
for (let i = 0; i < isolatedNodes.length; i++) {
|
|
347
|
-
const idCard = idCards.find((card) => card.id === isolatedNodes[i]);
|
|
348
|
-
if (!youngestNode || idCard.birthdate > youngestNode.birthdate) {
|
|
349
|
-
youngestNode = idCard;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (isIsolated) {
|
|
353
|
-
for (let i = 0; !candidates && i < eligibleSplits.length; i++) {
|
|
354
|
-
if (eligibleSplits[i].includes(youngestNode.id)) {
|
|
355
|
-
candidates = _.intersection(eligibleSplits[i], isolatedNodes);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
candidates = [youngestNode.id];
|
|
361
|
-
}
|
|
362
|
-
}
|
|
388
|
+
const candidates = this.electShutdownCandidates(splits, idCards);
|
|
363
389
|
if (candidates.includes(this.nodeId)) {
|
|
364
390
|
this.logger.error("[CLUSTER] Network split detected. This node is outside the cluster: shutting down.");
|
|
365
391
|
global.kuzzle.shutdown();
|
|
@@ -368,10 +394,160 @@ class ClusterNode {
|
|
|
368
394
|
}
|
|
369
395
|
catch (err) {
|
|
370
396
|
this.logger.error("[CLUSTER] Unexpected exception caught during a cluster consistency check. Shutting down...");
|
|
371
|
-
this.logger.error(err.stack);
|
|
397
|
+
this.logger.error(err instanceof Error ? err.stack : (0, util_1.inspect)(err));
|
|
372
398
|
global.kuzzle.shutdown();
|
|
373
399
|
}
|
|
374
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Builds the distinct topologies the cluster's ID cards disagree on. A
|
|
403
|
+
* topology shorter than the number of known cards is a split: that card sees
|
|
404
|
+
* fewer nodes than exist.
|
|
405
|
+
*
|
|
406
|
+
* Extracted from `enforceClusterConsistency()` unchanged — a pure function of
|
|
407
|
+
* the cards it is given, which is what makes moving it safe.
|
|
408
|
+
*/
|
|
409
|
+
detectSplits(idCards) {
|
|
410
|
+
const splits = [];
|
|
411
|
+
for (const idCard of idCards) {
|
|
412
|
+
let topology = Array.from(idCard.topology);
|
|
413
|
+
topology.push(idCard.id);
|
|
414
|
+
if (topology.length !== idCards.length) {
|
|
415
|
+
// Explicit comparator rather than the default: identical for these
|
|
416
|
+
// ASCII node ids, and the split election compares the sorted arrays
|
|
417
|
+
// element by element, so the ordering is load-bearing.
|
|
418
|
+
topology = topology.sort((a, b) => {
|
|
419
|
+
if (a === b) {
|
|
420
|
+
return 0;
|
|
421
|
+
}
|
|
422
|
+
return a < b ? -1 : 1;
|
|
423
|
+
});
|
|
424
|
+
const found = splits.some((split) => {
|
|
425
|
+
if (split.length !== topology.length) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
return split.every((id, index) => id === topology[index]);
|
|
429
|
+
});
|
|
430
|
+
if (!found) {
|
|
431
|
+
splits.push(topology);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return splits;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Decides which nodes must shut themselves down, given the detected splits.
|
|
439
|
+
*
|
|
440
|
+
* Extracted from `enforceClusterConsistency()` unchanged. Every node runs
|
|
441
|
+
* this on its own and must reach the same list, so it is a pure function of
|
|
442
|
+
* `splits` and `idCards` — no clock, no randomness, no state.
|
|
443
|
+
*/
|
|
444
|
+
electShutdownCandidates(splits, idCards) {
|
|
445
|
+
// There is at least 1 cluster split detected.
|
|
446
|
+
//
|
|
447
|
+
// First we elect the smallest split possible.
|
|
448
|
+
// If multiple splits are eligibles, we choose amongst them the split
|
|
449
|
+
// containing the youngest isolated node (isolated = the node is in this
|
|
450
|
+
// split alone, and in no other splits). If no split is eligible using
|
|
451
|
+
// this method, we fall back to the smallest split containing the youngest
|
|
452
|
+
// node (isolated or not).
|
|
453
|
+
//
|
|
454
|
+
// The goal of this process is to force at least 1 node to kill itself,
|
|
455
|
+
// with all nodes concluding on their own on the same list of nodes to
|
|
456
|
+
// shut down.
|
|
457
|
+
// First remove every non existing node from topologies
|
|
458
|
+
splits = splits.map((topology) => topology.filter((nodeId) => idCards.find((card) => card.id === nodeId)));
|
|
459
|
+
splits = splits.sort((a, b) => a.length - b.length);
|
|
460
|
+
const [smallest] = splits;
|
|
461
|
+
// The caller only reaches here with at least one split — the empty case
|
|
462
|
+
// returns before — and the sort then makes `splits[0]` the smallest.
|
|
463
|
+
if (smallest === undefined) {
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
const eligibleSplits = splits.filter((split) => split.length === smallest.length);
|
|
467
|
+
const [onlyEligible] = eligibleSplits;
|
|
468
|
+
if (eligibleSplits.length === 1 && onlyEligible !== undefined) {
|
|
469
|
+
return onlyEligible;
|
|
470
|
+
}
|
|
471
|
+
return this.electFromTiedSplits(eligibleSplits, splits, idCards);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Breaks a tie between equally small splits: prefer the one holding the
|
|
475
|
+
* youngest *isolated* node (present in that split and no other), and fall
|
|
476
|
+
* back to the youngest node overall.
|
|
477
|
+
*
|
|
478
|
+
* Extracted from `electShutdownCandidates()` unchanged, and pure like it —
|
|
479
|
+
* every node must reach the same answer from the same inputs.
|
|
480
|
+
*/
|
|
481
|
+
electFromTiedSplits(eligibleSplits, splits, idCards) {
|
|
482
|
+
// Beware: search isolated nodes in ALL the splits, not only the
|
|
483
|
+
// smallest ones
|
|
484
|
+
let isolatedNodes = (0, xor_1.default)(...splits);
|
|
485
|
+
const eligibleNodes = [...new Set(eligibleSplits.flat())];
|
|
486
|
+
isolatedNodes = (0, intersection_1.default)(isolatedNodes, eligibleNodes);
|
|
487
|
+
const isIsolated = isolatedNodes.length > 0;
|
|
488
|
+
// safety measure: this should never happen
|
|
489
|
+
if (isolatedNodes.length === 0) {
|
|
490
|
+
isolatedNodes = eligibleNodes;
|
|
491
|
+
}
|
|
492
|
+
let youngestNode;
|
|
493
|
+
for (const isolatedNode of isolatedNodes) {
|
|
494
|
+
const idCard = idCards.find((card) => card.id === isolatedNode);
|
|
495
|
+
// `find` answers undefined for a node whose ID card has expired since
|
|
496
|
+
// the topologies were read. It was dereferenced for its birthdate.
|
|
497
|
+
if (idCard === undefined) {
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (!youngestNode || idCard.birthdate > youngestNode.birthdate) {
|
|
501
|
+
youngestNode = idCard;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// Every isolated node's ID card is gone: there is nobody left to elect.
|
|
505
|
+
if (youngestNode === undefined) {
|
|
506
|
+
return [];
|
|
507
|
+
}
|
|
508
|
+
if (!isIsolated) {
|
|
509
|
+
return [youngestNode.id];
|
|
510
|
+
}
|
|
511
|
+
for (const split of eligibleSplits) {
|
|
512
|
+
if (split.includes(youngestNode.id)) {
|
|
513
|
+
return (0, intersection_1.default)(split, isolatedNodes);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return [];
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Handles a full-state round that no node answered: gives up if this was
|
|
520
|
+
* already the second attempt, otherwise drops every subscriber and waits a
|
|
521
|
+
* redis heartbeat round so the discovery can be redone against ID cards that
|
|
522
|
+
* may since have expired.
|
|
523
|
+
*
|
|
524
|
+
* Extracted from `handshake()` unchanged.
|
|
525
|
+
*
|
|
526
|
+
* @returns false when the caller must stop — the cluster is split and this
|
|
527
|
+
* node is shutting down
|
|
528
|
+
*/
|
|
529
|
+
async retryFullState(retried) {
|
|
530
|
+
// Uh oh... no node was able to give us the full state.
|
|
531
|
+
// We must retry later, to check if the redis keys have expired. If they
|
|
532
|
+
// are still there and we still aren't able to fetch a full state, this
|
|
533
|
+
// means we're probably facing a network split, and we must then shut
|
|
534
|
+
// down.
|
|
535
|
+
if (retried) {
|
|
536
|
+
this.logger.error("[CLUSTER] Could not connect to discovered cluster nodes (network split detected). Shutting down.");
|
|
537
|
+
global.kuzzle.shutdown();
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
// Disposes all subscribers
|
|
541
|
+
for (const subscriber of this.remoteNodes.values()) {
|
|
542
|
+
subscriber.dispose();
|
|
543
|
+
}
|
|
544
|
+
this.remoteNodes.clear();
|
|
545
|
+
// Waits for a redis heartbeat round
|
|
546
|
+
const retryDelay = this.heartbeatDelay * 1.5;
|
|
547
|
+
this.logger.warn(`[CLUSTER] Unable to connect to discovered cluster nodes. Retrying in ${retryDelay}ms...`);
|
|
548
|
+
await bluebird_1.default.delay(retryDelay);
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
375
551
|
/**
|
|
376
552
|
* Discovers other active nodes from the cluster and, if other nodes exist,
|
|
377
553
|
* starts a handshake procedure to sync this node and to make it able to
|
|
@@ -384,9 +560,11 @@ class ClusterNode {
|
|
|
384
560
|
this.logger.error(`[CLUSTER] Failed to join the cluster: timed out (joinTimeout: ${this.config.joinTimeout}ms)`);
|
|
385
561
|
global.kuzzle.shutdown();
|
|
386
562
|
}, this.config.joinTimeout);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
563
|
+
// NOSONAR (S1874): same deferral as the import above — every node takes
|
|
564
|
+
// "clusterHandshake" with `Mutex`, so converting one site to `withLock`
|
|
565
|
+
// would remove the exclusion it exists for. #2894.
|
|
566
|
+
const lockOptions = { timeout: this.config.joinTimeout };
|
|
567
|
+
const mutex = new mutex_1.Mutex("clusterHandshake", lockOptions); // NOSONAR
|
|
390
568
|
try {
|
|
391
569
|
await mutex.lock();
|
|
392
570
|
// Create the ID Key AFTER the handshake mutex is actually locked,
|
|
@@ -394,8 +572,10 @@ class ClusterNode {
|
|
|
394
572
|
// node while it's still initializing)
|
|
395
573
|
await this.idCardHandler.createIdCard();
|
|
396
574
|
debug("[CLUSTER] ID Card created");
|
|
397
|
-
|
|
398
|
-
|
|
575
|
+
const nodeId = this.idCardHandler.nodeId;
|
|
576
|
+
(0, assert_1.default)(nodeId !== null, "[CLUSTER] createIdCard() returned without reserving a node id");
|
|
577
|
+
this.nodeId = nodeId;
|
|
578
|
+
await this.startHeartbeat(); // NOSONAR: TD-26
|
|
399
579
|
debug("[CLUSTER] Start heartbeat");
|
|
400
580
|
let retried = false;
|
|
401
581
|
let fullState = null;
|
|
@@ -412,64 +592,35 @@ class ClusterNode {
|
|
|
412
592
|
// Verify that no other node share the same IP address as this one
|
|
413
593
|
const duplicate = nodes.filter((node) => node.ip === this.ip);
|
|
414
594
|
if (duplicate.length > 0) {
|
|
415
|
-
this.logger.error(`[CLUSTER] Another node share the same IP address as this one (${this.ip}): ${duplicate[0]
|
|
595
|
+
this.logger.error(`[CLUSTER] Another node share the same IP address as this one (${this.ip}): ${duplicate[0]?.id}. Shutting down.`);
|
|
416
596
|
global.kuzzle.shutdown();
|
|
417
597
|
return;
|
|
418
598
|
}
|
|
419
|
-
|
|
420
|
-
await Bluebird.map(nodes, ({ id, ip }) => {
|
|
421
|
-
const subscriber = new ClusterSubscriber(this, id, ip);
|
|
422
|
-
this.remoteNodes.set(id, subscriber);
|
|
423
|
-
return subscriber.init();
|
|
424
|
-
});
|
|
599
|
+
await this.subscribeToNodes(nodes);
|
|
425
600
|
debug("[CLUSTER] Successfully subscribed to nodes");
|
|
426
601
|
fullState = await this.command.getFullState(nodes);
|
|
427
|
-
// Uh oh... no node was able to give us the full state.
|
|
428
|
-
// We must retry later, to check if the redis keys have expired. If they
|
|
429
|
-
// are still there and we still aren't able to fetch a full state, this
|
|
430
|
-
// means we're probably facing a network split, and we must then shut
|
|
431
|
-
// down.
|
|
432
602
|
if (fullState === null) {
|
|
433
|
-
if (retried) {
|
|
434
|
-
this.logger.error("[CLUSTER] Could not connect to discovered cluster nodes (network split detected). Shutting down.");
|
|
435
|
-
global.kuzzle.shutdown();
|
|
603
|
+
if (!(await this.retryFullState(retried))) {
|
|
436
604
|
return;
|
|
437
605
|
}
|
|
438
|
-
// Disposes all subscribers
|
|
439
|
-
for (const subscriber of this.remoteNodes.values()) {
|
|
440
|
-
subscriber.dispose();
|
|
441
|
-
}
|
|
442
|
-
this.remoteNodes.clear();
|
|
443
|
-
// Waits for a redis heartbeat round
|
|
444
606
|
retried = true;
|
|
445
|
-
const retryDelay = this.heartbeatDelay * 1.5;
|
|
446
|
-
this.logger.warn(`[CLUSTER] Unable to connect to discovered cluster nodes. Retrying in ${retryDelay}ms...`);
|
|
447
|
-
await Bluebird.delay(retryDelay);
|
|
448
607
|
}
|
|
449
608
|
} while (fullState === null);
|
|
450
609
|
debug("[CLUSTER] Fullstate retrieved, loading into node..");
|
|
451
|
-
await this.fullState.loadFullState(fullState);
|
|
610
|
+
await this.fullState.loadFullState(fullState); // NOSONAR: TD-26
|
|
452
611
|
this.activity = fullState.activity ? fullState.activity : this.activity;
|
|
453
612
|
debug("[CLUSTER] Fullstate loaded.");
|
|
454
613
|
const handshakeResponses = await this.command.broadcastHandshake(nodes);
|
|
455
614
|
debug("[CLUSTER] Successful handshakes with other nodes.");
|
|
456
|
-
|
|
457
|
-
//
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
await this.idCardHandler.addNode(nodeId);
|
|
466
|
-
const nodesStates = fullState.nodesState || [];
|
|
467
|
-
const nodeStatus = nodesStates.find((node) => node.id === nodeId);
|
|
468
|
-
subscriber.sync(nodeStatus ? nodeStatus.lastMessageId : handshakeData.lastMessageId);
|
|
469
|
-
this.logger.info(`[CLUSTER] Successfully completed the handshake with node ${nodeId}`);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
this.logger.info("[CLUSTER] Successfully joined the cluster.");
|
|
615
|
+
await this.syncWithPeers(handshakeResponses, fullState);
|
|
616
|
+
// `createIdCard()` adopts `global.nodeId`, so these are normally the same
|
|
617
|
+
// string and the line reads as one name (TD-59, #2764). They still differ
|
|
618
|
+
// in the two cases the adoption cannot cover — a stale IdCard holding the
|
|
619
|
+
// key, and a Kuzzle running without a Backend — and those are exactly the
|
|
620
|
+
// cases where a reader needs to be told, so the line says both.
|
|
621
|
+
this.logger.info(global.nodeId && global.nodeId !== this.nodeId
|
|
622
|
+
? `[CLUSTER] Successfully joined the cluster as "${this.nodeId}" (process node id: ${global.nodeId}).`
|
|
623
|
+
: `[CLUSTER] Successfully joined the cluster as "${this.nodeId}".`);
|
|
473
624
|
this.trackActivity(this.nodeId, this.ip, nodeActivityEnum.ADDED);
|
|
474
625
|
}
|
|
475
626
|
finally {
|
|
@@ -477,6 +628,85 @@ class ClusterNode {
|
|
|
477
628
|
await mutex.unlock();
|
|
478
629
|
}
|
|
479
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* Subscribes to every discovered node and starts buffering their sync
|
|
633
|
+
* messages.
|
|
634
|
+
*
|
|
635
|
+
* Extracted from `handshake()` unchanged, including its awaits: the wait
|
|
636
|
+
* below is what makes the `getFullState` that follows it safe, so the
|
|
637
|
+
* ordering is the point of the method, not an implementation detail.
|
|
638
|
+
*/
|
|
639
|
+
async subscribeToNodes(nodes) {
|
|
640
|
+
// Subscribe to remote nodes and start buffering sync messages.
|
|
641
|
+
//
|
|
642
|
+
// The wait is what makes `getFullState` below safe: it snapshots each
|
|
643
|
+
// node's `lastMessageId` on the *command* channel, and this
|
|
644
|
+
// subscription lives on the *sync* channel, so nothing orders the
|
|
645
|
+
// snapshot after the subscription taking effect. Until it does, the
|
|
646
|
+
// remote's PUB socket drops what it publishes — and this node is then
|
|
647
|
+
// told to resume from just before the dropped message. See TD-65
|
|
648
|
+
// (#2773).
|
|
649
|
+
await bluebird_1.default.map(nodes, async ({ id, ip }) => {
|
|
650
|
+
const subscriber = new subscriber_1.default(this, id, ip);
|
|
651
|
+
this.remoteNodes.set(id, subscriber);
|
|
652
|
+
await subscriber.init();
|
|
653
|
+
const live = await subscriber.waitForSubscription(this.heartbeatDelay * 2);
|
|
654
|
+
if (!live) {
|
|
655
|
+
// Not fatal: a node whose ID card outlived it is exactly what the
|
|
656
|
+
// `fullState === null` retry below exists for. Say so, and let that
|
|
657
|
+
// path decide.
|
|
658
|
+
this.logger.warn(`[CLUSTER] No sync message received from node ${id} within ${this.heartbeatDelay * 2}ms: proceeding with a subscription that is not proven live.`);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Starts synchronizing with each peer that answered the handshake, and drops
|
|
664
|
+
* the ones that did not.
|
|
665
|
+
*
|
|
666
|
+
* Extracted from `handshake()` unchanged. `sync()` is awaited here for the
|
|
667
|
+
* reason written inside: a gap found during its replay evicts this node, and
|
|
668
|
+
* the success line must not be printed before that is known.
|
|
669
|
+
*/
|
|
670
|
+
async syncWithPeers(handshakeResponses,
|
|
671
|
+
// `NonNullable`: the caller loops until the full state is not null, and
|
|
672
|
+
// this reads `nodesState` off it.
|
|
673
|
+
fullState) {
|
|
674
|
+
// Update subscribers: start synchronizing, or unsubscribes from nodes who
|
|
675
|
+
// didn't respond
|
|
676
|
+
for (const [nodeId, handshakeData] of Object.entries(handshakeResponses)) {
|
|
677
|
+
const subscriber = this.remoteNodes.get(nodeId);
|
|
678
|
+
// A response from a node this one never subscribed to: there is nothing
|
|
679
|
+
// to dispose of and nothing to sync. `subscribeToNodes` fills the map
|
|
680
|
+
// from the same list the handshake was broadcast to, so this is the
|
|
681
|
+
// gap between the two — and both branches below dereferenced it.
|
|
682
|
+
if (subscriber === undefined) {
|
|
683
|
+
this.remoteNodes.delete(nodeId);
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
if (handshakeData === null) {
|
|
687
|
+
subscriber.dispose();
|
|
688
|
+
this.remoteNodes.delete(nodeId);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
await this.idCardHandler.addNode(nodeId);
|
|
692
|
+
const nodesStates = fullState.nodesState || [];
|
|
693
|
+
const nodeStatus = nodesStates.find((node) => node.id === nodeId);
|
|
694
|
+
// Protobuf leaves an absent field undefined, and zero is where a
|
|
695
|
+
// subscriber that has seen nothing starts.
|
|
696
|
+
const lastMessageId = nodeStatus?.lastMessageId ??
|
|
697
|
+
handshakeData.lastMessageId ??
|
|
698
|
+
new long_1.default(0, 0, true);
|
|
699
|
+
// Awaited: `sync()` replays the buffered messages, and a gap found
|
|
700
|
+
// there evicts this node. Fired and forgotten, it used to print
|
|
701
|
+
// "Successfully completed the handshake" 2ms AFTER the eviction that
|
|
702
|
+
// contradicts it — a log describing a state the code is not in.
|
|
703
|
+
const synced = await subscriber.sync(lastMessageId);
|
|
704
|
+
if (synced) {
|
|
705
|
+
this.logger.info(`[CLUSTER] Successfully completed the handshake with node ${nodeId}`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
480
710
|
countActiveNodes() {
|
|
481
711
|
return this.remoteNodes.size + 1;
|
|
482
712
|
}
|
|
@@ -522,7 +752,10 @@ class ClusterNode {
|
|
|
522
752
|
nodes: [],
|
|
523
753
|
};
|
|
524
754
|
const idCards = await this.idCardHandler.getRemoteIdCards();
|
|
525
|
-
|
|
755
|
+
const ownIdCard = this.idCardHandler.idCard;
|
|
756
|
+
if (ownIdCard !== null) {
|
|
757
|
+
idCards.push(ownIdCard);
|
|
758
|
+
}
|
|
526
759
|
for (const idCard of idCards) {
|
|
527
760
|
status.nodes.push({
|
|
528
761
|
address: idCard.ip,
|
|
@@ -665,7 +898,7 @@ class ClusterNode {
|
|
|
665
898
|
onNewRealtimeRoom(payload) {
|
|
666
899
|
const roomMessageId = this.publisher.sendNewRealtimeRoom(payload);
|
|
667
900
|
debug("[%s] Broadcasting new realtime room %s (message: %d)", this.nodeId, payload.id, roomMessageId);
|
|
668
|
-
const icpair = fromKoncordeIndex(payload.index);
|
|
901
|
+
const icpair = (0, koncordeCompat_1.fromKoncordeIndex)(payload.index);
|
|
669
902
|
this.fullState.addRealtimeRoom(payload.id, icpair.index, icpair.collection, payload.filter, {
|
|
670
903
|
messageId: roomMessageId,
|
|
671
904
|
nodeId: this.nodeId,
|