@celerispay/hazelcast-client 3.12.5-8 → 3.12.7-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/CHANGES_UNCOMMITTED.md +53 -0
- package/FAULT_TOLERANCE_IMPROVEMENTS.md +208 -0
- package/HAZELCAST_CLIENT_EVOLUTION.md +402 -0
- package/lib/HeartbeatService.js +11 -2
- package/lib/PartitionService.d.ts +0 -3
- package/lib/PartitionService.js +3 -32
- package/lib/invocation/ClientConnection.js +41 -11
- package/lib/invocation/ClientConnectionManager.d.ts +54 -0
- package/lib/invocation/ClientConnectionManager.js +210 -4
- package/lib/invocation/ClusterService.d.ts +47 -0
- package/lib/invocation/ClusterService.js +164 -4
- package/lib/invocation/ConnectionAuthenticator.d.ts +11 -0
- package/lib/invocation/ConnectionAuthenticator.js +85 -12
- package/lib/invocation/CredentialPreservationService.d.ts +141 -0
- package/lib/invocation/CredentialPreservationService.js +377 -0
- package/lib/invocation/HazelcastFailoverManager.d.ts +102 -0
- package/lib/invocation/HazelcastFailoverManager.js +285 -0
- package/package.json +7 -6
package/lib/PartitionService.js
CHANGED
|
@@ -23,9 +23,6 @@ var PartitionService = /** @class */ (function () {
|
|
|
23
23
|
this.partitionMap = {};
|
|
24
24
|
this.lastRefreshTime = 0;
|
|
25
25
|
this.minRefreshInterval = 2000; // Minimum 2 seconds between refreshes
|
|
26
|
-
this.refreshInProgress = false;
|
|
27
|
-
this.maxRefreshRetries = 3;
|
|
28
|
-
this.refreshRetryCount = 0;
|
|
29
26
|
this.client = client;
|
|
30
27
|
this.logger = client.getLoggingService().getLogger();
|
|
31
28
|
this.isShutdown = false;
|
|
@@ -37,7 +34,6 @@ var PartitionService = /** @class */ (function () {
|
|
|
37
34
|
PartitionService.prototype.shutdown = function () {
|
|
38
35
|
clearInterval(this.partitionRefreshTask);
|
|
39
36
|
this.isShutdown = true;
|
|
40
|
-
this.refreshInProgress = false;
|
|
41
37
|
};
|
|
42
38
|
/**
|
|
43
39
|
* Clears the partition table, forcing a refresh on next operation
|
|
@@ -47,7 +43,6 @@ var PartitionService = /** @class */ (function () {
|
|
|
47
43
|
this.partitionMap = {};
|
|
48
44
|
this.partitionCount = 0;
|
|
49
45
|
this.lastRefreshTime = 0;
|
|
50
|
-
this.refreshRetryCount = 0;
|
|
51
46
|
};
|
|
52
47
|
/**
|
|
53
48
|
* Refreshes the internal partition table.
|
|
@@ -62,50 +57,26 @@ var PartitionService = /** @class */ (function () {
|
|
|
62
57
|
this.logger.debug('PartitionService', 'Skipping refresh, too soon since last refresh');
|
|
63
58
|
return Promise.resolve();
|
|
64
59
|
}
|
|
65
|
-
if (this.refreshInProgress) {
|
|
66
|
-
this.logger.debug('PartitionService', 'Refresh already in progress, skipping');
|
|
67
|
-
return Promise.resolve();
|
|
68
|
-
}
|
|
69
60
|
var ownerConnection = this.client.getClusterService().getOwnerConnection();
|
|
70
61
|
if (ownerConnection == null) {
|
|
71
62
|
this.logger.warn('PartitionService', 'Cannot refresh partitions, no owner connection available');
|
|
72
|
-
|
|
73
|
-
return Promise.reject(new Error('No owner connection available for partition refresh'));
|
|
63
|
+
return Promise.resolve();
|
|
74
64
|
}
|
|
75
|
-
this.refreshInProgress = true;
|
|
76
65
|
var clientMessage = GetPartitionsCodec.encodeRequest();
|
|
77
|
-
|
|
78
|
-
var refreshPromise = this.client.getInvocationService()
|
|
66
|
+
return this.client.getInvocationService()
|
|
79
67
|
.invokeOnConnection(ownerConnection, clientMessage)
|
|
80
68
|
.then(function (response) {
|
|
81
69
|
var receivedPartitionMap = GetPartitionsCodec.decodeResponse(response);
|
|
82
70
|
_this.partitionMap = receivedPartitionMap;
|
|
83
71
|
_this.partitionCount = Object.keys(_this.partitionMap).length;
|
|
84
72
|
_this.lastRefreshTime = now;
|
|
85
|
-
_this.refreshRetryCount = 0; // Reset retry count on success
|
|
86
73
|
_this.logger.debug('PartitionService', "Refreshed partition table with " + _this.partitionCount + " partitions");
|
|
87
74
|
}).catch(function (e) {
|
|
88
75
|
if (_this.client.getLifecycleService().isRunning()) {
|
|
89
76
|
_this.logger.warn('PartitionService', 'Error while fetching cluster partition table from '
|
|
90
|
-
+ _this.client.getClusterService().ownerUuid, e);
|
|
91
|
-
// Increment retry count and clear table if too many failures
|
|
92
|
-
_this.refreshRetryCount++;
|
|
93
|
-
if (_this.refreshRetryCount >= _this.maxRefreshRetries) {
|
|
94
|
-
_this.logger.error('PartitionService', "Max refresh retries (" + _this.maxRefreshRetries + ") exceeded, clearing partition table");
|
|
95
|
-
_this.clearPartitionTable();
|
|
96
|
-
}
|
|
77
|
+
+ _this.client.getClusterService().ownerUuid || 'unknown', e);
|
|
97
78
|
}
|
|
98
|
-
throw e; // Re-throw the error to propagate it
|
|
99
|
-
}).finally(function () {
|
|
100
|
-
_this.refreshInProgress = false;
|
|
101
|
-
});
|
|
102
|
-
// Add timeout to prevent hanging
|
|
103
|
-
var timeoutPromise = new Promise(function (_, reject) {
|
|
104
|
-
setTimeout(function () {
|
|
105
|
-
reject(new Error('Partition refresh timed out'));
|
|
106
|
-
}, 10000); // 10 second timeout
|
|
107
79
|
});
|
|
108
|
-
return Promise.race([refreshPromise, timeoutPromise]);
|
|
109
80
|
};
|
|
110
81
|
/**
|
|
111
82
|
* Returns the {@link Address} of the node which owns given partition id.
|
|
@@ -231,20 +231,28 @@ var ClientConnection = /** @class */ (function () {
|
|
|
231
231
|
_this.closedTime = Date.now();
|
|
232
232
|
_this.logger.debug('ClientConnection', "Socket closed for " + _this.address.toString());
|
|
233
233
|
});
|
|
234
|
-
// Handle socket end events
|
|
234
|
+
// Handle socket end events - be more resilient to temporary disconnections
|
|
235
235
|
this.socket.on('end', function () {
|
|
236
|
-
_this.closedTime = Date.now();
|
|
237
236
|
_this.logger.debug('ClientConnection', "Socket ended by remote for " + _this.address.toString());
|
|
237
|
+
// Don't immediately mark as closed - this could be a temporary network hiccup
|
|
238
|
+
// Only mark as closed if we're certain it's a real disconnection
|
|
239
|
+
// The health check will handle this more gracefully
|
|
238
240
|
});
|
|
239
|
-
// Handle socket errors
|
|
241
|
+
// Handle socket errors - be more resilient
|
|
240
242
|
this.socket.on('error', function (error) {
|
|
241
243
|
_this.logger.warn('ClientConnection', "Socket error for " + _this.address.toString() + ":", error);
|
|
242
|
-
//
|
|
244
|
+
// Only mark as closed for specific error types that indicate real disconnection
|
|
245
|
+
if (error.code === 'ECONNRESET' || error.code === 'EPIPE') {
|
|
246
|
+
_this.logger.warn('ClientConnection', "Critical socket error for " + _this.address.toString() + ", marking as closed");
|
|
247
|
+
_this.closedTime = Date.now();
|
|
248
|
+
}
|
|
249
|
+
// For other errors, don't immediately close - let health check handle it
|
|
243
250
|
});
|
|
244
|
-
// Handle socket timeout
|
|
251
|
+
// Handle socket timeout - be more resilient
|
|
245
252
|
this.socket.on('timeout', function () {
|
|
246
253
|
_this.logger.warn('ClientConnection', "Socket timeout for " + _this.address.toString());
|
|
247
254
|
// Don't set closedTime here as the socket might still be usable
|
|
255
|
+
// Let the health check handle this more gracefully
|
|
248
256
|
});
|
|
249
257
|
};
|
|
250
258
|
/**
|
|
@@ -280,8 +288,27 @@ var ClientConnection = /** @class */ (function () {
|
|
|
280
288
|
* Closes this connection.
|
|
281
289
|
*/
|
|
282
290
|
ClientConnection.prototype.close = function () {
|
|
291
|
+
var addressStr = this.address.toString();
|
|
292
|
+
// Log who is initiating the disconnection
|
|
293
|
+
this.logger.info('ClientConnection', "\uD83D\uDD0C CLIENT INITIATED DISCONNECTION from " + addressStr);
|
|
294
|
+
// Log the call stack to understand why this is being called
|
|
295
|
+
this.logger.info('ClientConnection', "\uD83D\uDD0D Close Connection Call Stack for " + addressStr + ":");
|
|
296
|
+
var stack = new Error().stack;
|
|
297
|
+
if (stack) {
|
|
298
|
+
this.logger.info('ClientConnection', " - Called from: " + stack.split('\n').slice(1, 4).join(' | '));
|
|
299
|
+
}
|
|
300
|
+
// Log connection state at closure
|
|
301
|
+
this.logger.info('ClientConnection', "\uD83D\uDCCA Connection State at Closure for " + addressStr + ":");
|
|
302
|
+
this.logger.info('ClientConnection', " - Is Alive: " + this.isAlive());
|
|
303
|
+
this.logger.info('ClientConnection', " - Is Healthy: " + this.isHealthy());
|
|
304
|
+
this.logger.info('ClientConnection', " - Is Heartbeating: " + this.isHeartbeating());
|
|
305
|
+
this.logger.info('ClientConnection', " - Is Owner: " + this.isAuthenticatedAsOwner());
|
|
306
|
+
this.logger.info('ClientConnection', " - Last Read: " + (this.lastReadTimeMillis > 0 ? Math.round((Date.now() - this.lastReadTimeMillis) / 1000) + 's ago' : 'never'));
|
|
307
|
+
this.logger.info('ClientConnection', " - Last Write: " + (this.lastWriteTimeMillis > 0 ? Math.round((Date.now() - this.lastWriteTimeMillis) / 1000) + 's ago' : 'never'));
|
|
308
|
+
this.logger.info('ClientConnection', " - Connection Age: " + Math.round((Date.now() - this.startTime) / 1000) + "s");
|
|
283
309
|
this.socket.end();
|
|
284
310
|
this.closedTime = Date.now();
|
|
311
|
+
this.logger.info('ClientConnection', "\u2705 Disconnection completed for " + addressStr);
|
|
285
312
|
};
|
|
286
313
|
/**
|
|
287
314
|
* Checks if the connection is alive and healthy
|
|
@@ -300,11 +327,12 @@ var ClientConnection = /** @class */ (function () {
|
|
|
300
327
|
if (!this.socket.writable) {
|
|
301
328
|
return false;
|
|
302
329
|
}
|
|
303
|
-
//
|
|
330
|
+
// More resilient data freshness check - only fail if REALLY stale
|
|
304
331
|
var now = Date.now();
|
|
305
|
-
var lastReadThreshold =
|
|
332
|
+
var lastReadThreshold = 120000; // Increased from 30s to 2 minutes for production resilience
|
|
306
333
|
if (this.lastReadTimeMillis > 0 && (now - this.lastReadTimeMillis) > lastReadThreshold) {
|
|
307
|
-
//
|
|
334
|
+
// Only mark as dead if we haven't received data for 2 minutes
|
|
335
|
+
// This prevents false drops due to temporary network hiccups
|
|
308
336
|
return false;
|
|
309
337
|
}
|
|
310
338
|
return true;
|
|
@@ -314,12 +342,14 @@ var ClientConnection = /** @class */ (function () {
|
|
|
314
342
|
* @returns true if connection is healthy, false otherwise
|
|
315
343
|
*/
|
|
316
344
|
ClientConnection.prototype.isHealthy = function () {
|
|
345
|
+
var addressStr = this.address.toString();
|
|
317
346
|
if (!this.isAlive()) {
|
|
318
347
|
return false;
|
|
319
348
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
349
|
+
if (!this.isHeartbeating()) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
323
353
|
};
|
|
324
354
|
ClientConnection.prototype.isHeartbeating = function () {
|
|
325
355
|
return this.heartbeating;
|
|
@@ -67,6 +67,56 @@ export declare class ClientConnectionManager extends EventEmitter {
|
|
|
67
67
|
getPendingConnections(): {
|
|
68
68
|
[address: string]: Promise.Resolver<ClientConnection>;
|
|
69
69
|
};
|
|
70
|
+
private credentialPreservationService;
|
|
71
|
+
private memberAddedEvents;
|
|
72
|
+
/**
|
|
73
|
+
* Updates preserved credentials with server-provided data
|
|
74
|
+
* @param address The address to update credentials for
|
|
75
|
+
* @param newUuid The new UUID from server member event
|
|
76
|
+
*/
|
|
77
|
+
updatePreservedCredentials(address: Address, newUuid: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* Records that a member added event was received from server
|
|
80
|
+
* @param address The address that had a server member added event
|
|
81
|
+
*/
|
|
82
|
+
recordMemberAddedEvent(address: Address): void;
|
|
83
|
+
/**
|
|
84
|
+
* Checks if we received a server member added event for an address
|
|
85
|
+
* @param address The address to check
|
|
86
|
+
* @returns True if server member added event was received
|
|
87
|
+
*/
|
|
88
|
+
hasMemberAddedEvent(address: Address): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Clears member added events for an address (useful during failover)
|
|
91
|
+
* @param address The address to clear events for
|
|
92
|
+
*/
|
|
93
|
+
clearMemberAddedEvents(address: Address): void;
|
|
94
|
+
/**
|
|
95
|
+
* Updates ALL credentials with server-provided owner UUID
|
|
96
|
+
* @param newOwnerUuid The new owner UUID from server cluster state
|
|
97
|
+
*/
|
|
98
|
+
updateAllCredentialsWithNewOwnerUuid(newOwnerUuid: string): void;
|
|
99
|
+
/**
|
|
100
|
+
* Gets the current owner UUID from server-stored credentials
|
|
101
|
+
* @returns The current owner UUID or null if not found
|
|
102
|
+
*/
|
|
103
|
+
getCurrentOwnerUuid(): string | null;
|
|
104
|
+
/**
|
|
105
|
+
* Validates that all stored credentials are consistent with server data
|
|
106
|
+
* @returns True if all credentials are consistent, false otherwise
|
|
107
|
+
*/
|
|
108
|
+
validateCredentialConsistency(): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Requests current cluster state from server when reconnecting
|
|
111
|
+
* This ensures we have the most up-to-date member information
|
|
112
|
+
* @returns Server cluster state
|
|
113
|
+
*/
|
|
114
|
+
requestServerClusterState(): any;
|
|
115
|
+
/**
|
|
116
|
+
* Gets the current owner connection for server requests
|
|
117
|
+
* @returns The owner connection or null if not available
|
|
118
|
+
*/
|
|
119
|
+
private getOwnerConnection();
|
|
70
120
|
/**
|
|
71
121
|
* Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
|
|
72
122
|
* it first connects to the address and then return the {@link ClientConnection}.
|
|
@@ -85,6 +135,10 @@ export declare class ClientConnectionManager extends EventEmitter {
|
|
|
85
135
|
* @param address The address to cleanup
|
|
86
136
|
*/
|
|
87
137
|
cleanupConnectionsForFailover(address: Address): void;
|
|
138
|
+
/**
|
|
139
|
+
* Clears all stored credentials - used for single-node cluster reset
|
|
140
|
+
*/
|
|
141
|
+
clearAllCredentials(): void;
|
|
88
142
|
/**
|
|
89
143
|
* Handles authentication errors by clearing failed connections and allowing retry
|
|
90
144
|
* @param address The address that had authentication issues
|
|
@@ -35,6 +35,7 @@ var net = require("net");
|
|
|
35
35
|
var tls = require("tls");
|
|
36
36
|
var Util_1 = require("../Util");
|
|
37
37
|
var BasicSSLOptionsFactory_1 = require("../connection/BasicSSLOptionsFactory");
|
|
38
|
+
var CredentialPreservationService_1 = require("./CredentialPreservationService");
|
|
38
39
|
var EMIT_CONNECTION_CLOSED = 'connectionClosed';
|
|
39
40
|
var EMIT_CONNECTION_OPENED = 'connectionOpened';
|
|
40
41
|
/**
|
|
@@ -51,10 +52,13 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
51
52
|
_this.connectionRetryDelay = 1000;
|
|
52
53
|
_this.connectionCleanupTask = null;
|
|
53
54
|
_this.connectionCleanupInterval = 15000; // 15 seconds between cleanup checks
|
|
55
|
+
_this.memberAddedEvents = new Set();
|
|
54
56
|
_this.client = client;
|
|
55
57
|
_this.logger = _this.client.getLoggingService().getLogger();
|
|
56
58
|
_this.addressTranslator = addressTranslator;
|
|
57
59
|
_this.addressProviders = addressProviders;
|
|
60
|
+
// Initialize credential preservation service for server data storage
|
|
61
|
+
_this.credentialPreservationService = new CredentialPreservationService_1.CredentialPreservationService(_this.logger);
|
|
58
62
|
_this.startConnectionHealthCheck();
|
|
59
63
|
_this.startConnectionCleanupTask();
|
|
60
64
|
return _this;
|
|
@@ -248,6 +252,129 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
248
252
|
ClientConnectionManager.prototype.getPendingConnections = function () {
|
|
249
253
|
return this.pendingConnections;
|
|
250
254
|
};
|
|
255
|
+
/**
|
|
256
|
+
* Updates preserved credentials with server-provided data
|
|
257
|
+
* @param address The address to update credentials for
|
|
258
|
+
* @param newUuid The new UUID from server member event
|
|
259
|
+
*/
|
|
260
|
+
ClientConnectionManager.prototype.updatePreservedCredentials = function (address, newUuid) {
|
|
261
|
+
var addressStr = address.toString();
|
|
262
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDD04 SERVER-FIRST: Updating credentials for " + addressStr + " with server UUID: " + newUuid);
|
|
263
|
+
// Get the current owner UUID from cluster service
|
|
264
|
+
var clusterService = this.client.getClusterService();
|
|
265
|
+
var currentOwnerUuid = clusterService.ownerUuid;
|
|
266
|
+
// Get group config from client
|
|
267
|
+
var groupConfig = this.client.getConfig().groupConfig;
|
|
268
|
+
// Store the server-provided UUID as the authoritative credential
|
|
269
|
+
// Use preserveCredentials to create new credentials if they don't exist
|
|
270
|
+
this.credentialPreservationService.preserveCredentials(address, newUuid, currentOwnerUuid || newUuid, // Use current owner UUID or fallback to member UUID
|
|
271
|
+
groupConfig.name, // Group name from config
|
|
272
|
+
groupConfig.password || '', // Group password from config
|
|
273
|
+
false // Not owner connection
|
|
274
|
+
);
|
|
275
|
+
// Mark that we received a member added event for this address
|
|
276
|
+
this.memberAddedEvents.add(addressStr);
|
|
277
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDCBE SERVER-FIRST: Stored server credential for " + addressStr + ": uuid=" + newUuid + ", ownerUuid=" + (currentOwnerUuid || newUuid));
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* Records that a member added event was received from server
|
|
281
|
+
* @param address The address that had a server member added event
|
|
282
|
+
*/
|
|
283
|
+
ClientConnectionManager.prototype.recordMemberAddedEvent = function (address) {
|
|
284
|
+
var addressStr = address.toString();
|
|
285
|
+
this.memberAddedEvents.add(addressStr);
|
|
286
|
+
this.logger.debug('ClientConnectionManager', "\uD83D\uDCDD SERVER-FIRST: Recorded server member added event for " + addressStr);
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* Checks if we received a server member added event for an address
|
|
290
|
+
* @param address The address to check
|
|
291
|
+
* @returns True if server member added event was received
|
|
292
|
+
*/
|
|
293
|
+
ClientConnectionManager.prototype.hasMemberAddedEvent = function (address) {
|
|
294
|
+
var addressStr = address.toString();
|
|
295
|
+
return this.memberAddedEvents.has(addressStr);
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Clears member added events for an address (useful during failover)
|
|
299
|
+
* @param address The address to clear events for
|
|
300
|
+
*/
|
|
301
|
+
ClientConnectionManager.prototype.clearMemberAddedEvents = function (address) {
|
|
302
|
+
var addressStr = address.toString();
|
|
303
|
+
this.memberAddedEvents.delete(addressStr);
|
|
304
|
+
this.logger.debug('ClientConnectionManager', "\uD83D\uDDD1\uFE0F SERVER-FIRST: Cleared member events for " + addressStr);
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* Updates ALL credentials with server-provided owner UUID
|
|
308
|
+
* @param newOwnerUuid The new owner UUID from server cluster state
|
|
309
|
+
*/
|
|
310
|
+
ClientConnectionManager.prototype.updateAllCredentialsWithNewOwnerUuid = function (newOwnerUuid) {
|
|
311
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDD04 SERVER-FIRST: Updating ALL credentials with server owner UUID: " + newOwnerUuid);
|
|
312
|
+
// Update all preserved credentials with server data
|
|
313
|
+
this.credentialPreservationService.updateAllOwnerUuids(newOwnerUuid);
|
|
314
|
+
// Validate consistency
|
|
315
|
+
var isConsistent = this.credentialPreservationService.validateOwnerUuidConsistency();
|
|
316
|
+
if (isConsistent) {
|
|
317
|
+
this.logger.info('ClientConnectionManager', "\u2705 SERVER-FIRST: All credentials now consistent with server owner UUID: " + newOwnerUuid);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
this.logger.warn('ClientConnectionManager', "\u26A0\uFE0F SERVER-FIRST: Credential consistency validation failed after server update");
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* Gets the current owner UUID from server-stored credentials
|
|
325
|
+
* @returns The current owner UUID or null if not found
|
|
326
|
+
*/
|
|
327
|
+
ClientConnectionManager.prototype.getCurrentOwnerUuid = function () {
|
|
328
|
+
return this.credentialPreservationService.getCurrentOwnerUuid();
|
|
329
|
+
};
|
|
330
|
+
/**
|
|
331
|
+
* Validates that all stored credentials are consistent with server data
|
|
332
|
+
* @returns True if all credentials are consistent, false otherwise
|
|
333
|
+
*/
|
|
334
|
+
ClientConnectionManager.prototype.validateCredentialConsistency = function () {
|
|
335
|
+
return this.credentialPreservationService.validateOwnerUuidConsistency();
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* Requests current cluster state from server when reconnecting
|
|
339
|
+
* This ensures we have the most up-to-date member information
|
|
340
|
+
* @returns Server cluster state
|
|
341
|
+
*/
|
|
342
|
+
ClientConnectionManager.prototype.requestServerClusterState = function () {
|
|
343
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDD0D SERVER-FIRST: Requesting current cluster state from server...");
|
|
344
|
+
try {
|
|
345
|
+
// Get the current owner connection
|
|
346
|
+
var ownerConnection = this.getOwnerConnection();
|
|
347
|
+
if (!ownerConnection || !ownerConnection.isAlive()) {
|
|
348
|
+
this.logger.warn('ClientConnectionManager', "\u26A0\uFE0F SERVER-FIRST: No active owner connection available for cluster state request");
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
// Request cluster state from the server
|
|
352
|
+
// This will give us the authoritative member list
|
|
353
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDCE4 SERVER-FIRST: Sending cluster state request to server...");
|
|
354
|
+
// For now, we'll use the existing cluster service to get member info
|
|
355
|
+
// In a full implementation, we'd send a specific protocol message
|
|
356
|
+
var clusterService = this.client.getClusterService();
|
|
357
|
+
var members = clusterService.getMembers();
|
|
358
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDCE5 SERVER-FIRST: Received cluster state from server: " + members.length + " members");
|
|
359
|
+
return {
|
|
360
|
+
members: members,
|
|
361
|
+
timestamp: Date.now(),
|
|
362
|
+
source: 'server'
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
this.logger.error('ClientConnectionManager', "\u274C SERVER-FIRST: Failed to request cluster state from server: " + error.message);
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Gets the current owner connection for server requests
|
|
372
|
+
* @returns The owner connection or null if not available
|
|
373
|
+
*/
|
|
374
|
+
ClientConnectionManager.prototype.getOwnerConnection = function () {
|
|
375
|
+
var clusterService = this.client.getClusterService();
|
|
376
|
+
return clusterService.getOwnerConnection();
|
|
377
|
+
};
|
|
251
378
|
/**
|
|
252
379
|
* Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
|
|
253
380
|
* it first connects to the address and then return the {@link ClientConnection}.
|
|
@@ -348,6 +475,17 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
348
475
|
// Remove from failed connections to allow reconnection after failover
|
|
349
476
|
this.failedConnections.delete(addressStr);
|
|
350
477
|
};
|
|
478
|
+
/**
|
|
479
|
+
* Clears all stored credentials - used for single-node cluster reset
|
|
480
|
+
*/
|
|
481
|
+
ClientConnectionManager.prototype.clearAllCredentials = function () {
|
|
482
|
+
this.logger.info('ClientConnectionManager', '🧹 Clearing all stored credentials for cluster reset');
|
|
483
|
+
// Clear all stored credentials
|
|
484
|
+
this.credentialPreservationService.clearAllCredentials();
|
|
485
|
+
// Clear all failed connections to allow fresh reconnection attempts
|
|
486
|
+
this.failedConnections.clear();
|
|
487
|
+
this.logger.info('ClientConnectionManager', '✅ All credentials cleared, ready for fresh authentication');
|
|
488
|
+
};
|
|
351
489
|
/**
|
|
352
490
|
* Handles authentication errors by clearing failed connections and allowing retry
|
|
353
491
|
* @param address The address that had authentication issues
|
|
@@ -387,9 +525,20 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
387
525
|
ClientConnectionManager.prototype.triggerConnect = function (address, asOwner) {
|
|
388
526
|
var _this = this;
|
|
389
527
|
if (!asOwner) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
528
|
+
var ownerConnection = this.client.getClusterService().getOwnerConnection();
|
|
529
|
+
if (ownerConnection == null) {
|
|
530
|
+
// Check if this is a single-node scenario
|
|
531
|
+
var knownAddresses = this.client.getClusterService().getKnownAddresses();
|
|
532
|
+
var isSingleNode = knownAddresses.length === 1;
|
|
533
|
+
if (isSingleNode) {
|
|
534
|
+
// Allow connection in single-node scenario
|
|
535
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDD17 SINGLE-NODE: Allowing connection to " + address.toString() + " (only node in cluster)");
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
// Multi-node scenario - require owner connection (preserve existing logic)
|
|
539
|
+
var error = new HazelcastError_1.IllegalStateError('Owner connection is not available!');
|
|
540
|
+
return Promise.reject(error);
|
|
541
|
+
}
|
|
393
542
|
}
|
|
394
543
|
}
|
|
395
544
|
if (this.client.getConfig().networkConfig.sslConfig.enabled) {
|
|
@@ -468,8 +617,65 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
468
617
|
this.emit(EMIT_CONNECTION_OPENED, connection);
|
|
469
618
|
};
|
|
470
619
|
ClientConnectionManager.prototype.authenticate = function (connection, ownerConnection) {
|
|
620
|
+
var _this = this;
|
|
621
|
+
var address = connection.getAddress();
|
|
622
|
+
var addressStr = address.toString();
|
|
623
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDD10 Starting authentication for " + addressStr + " (owner=" + ownerConnection + ")");
|
|
624
|
+
// Check if we have stored credentials for this address
|
|
625
|
+
var storedCredentials = this.credentialPreservationService.restoreCredentials(address);
|
|
626
|
+
var hasMemberAddedEvent = this.hasMemberAddedEvent(address);
|
|
627
|
+
if (storedCredentials) {
|
|
628
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDCCB Using STORED credentials for " + addressStr + ":");
|
|
629
|
+
this.logger.info('ClientConnectionManager', " - UUID: " + storedCredentials.uuid);
|
|
630
|
+
this.logger.info('ClientConnectionManager', " - Owner UUID: " + storedCredentials.ownerUuid);
|
|
631
|
+
this.logger.info('ClientConnectionManager', " - Group Name: " + storedCredentials.groupName);
|
|
632
|
+
this.logger.info('ClientConnectionManager', " - Member Added Event: " + (hasMemberAddedEvent ? 'YES' : 'NO'));
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDCCB No stored credentials found for " + addressStr + ", using fresh authentication");
|
|
636
|
+
}
|
|
637
|
+
// Log current cluster state for debugging
|
|
638
|
+
var clusterService = this.client.getClusterService();
|
|
639
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDD0D Current cluster state for " + addressStr + ":");
|
|
640
|
+
this.logger.info('ClientConnectionManager', " - Client UUID: " + (clusterService.uuid || 'NOT SET'));
|
|
641
|
+
this.logger.info('ClientConnectionManager', " - Client Owner UUID: " + (clusterService.ownerUuid || 'NOT SET'));
|
|
642
|
+
this.logger.info('ClientConnectionManager', " - Active Members: " + clusterService.getMembers().length);
|
|
643
|
+
// Log what we're about to send
|
|
644
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDCE4 Sending authentication request to " + addressStr + " with:");
|
|
645
|
+
this.logger.info('ClientConnectionManager', " - Owner Connection: " + ownerConnection);
|
|
646
|
+
this.logger.info('ClientConnectionManager', " - Using Stored Credentials: " + (storedCredentials ? 'YES' : 'NO'));
|
|
471
647
|
var authenticator = new ConnectionAuthenticator_1.ConnectionAuthenticator(connection, this.client);
|
|
472
|
-
|
|
648
|
+
// Use normal authentication flow - server handles everything
|
|
649
|
+
return authenticator.authenticate(ownerConnection)
|
|
650
|
+
.then(function () {
|
|
651
|
+
_this.logger.info('ClientConnectionManager', "\u2705 Authentication successful for " + addressStr);
|
|
652
|
+
// After successful authentication, store the new credentials from server
|
|
653
|
+
if (ownerConnection) {
|
|
654
|
+
var newUuid = clusterService.uuid;
|
|
655
|
+
var newOwnerUuid = clusterService.ownerUuid;
|
|
656
|
+
_this.logger.info('ClientConnectionManager', "\uD83D\uDCBE Storing new credentials from server for " + addressStr + ":");
|
|
657
|
+
_this.logger.info('ClientConnectionManager', " - New UUID: " + newUuid);
|
|
658
|
+
_this.logger.info('ClientConnectionManager', " - New Owner UUID: " + newOwnerUuid);
|
|
659
|
+
// Store the new credentials
|
|
660
|
+
_this.credentialPreservationService.updateCredentials(address, newUuid, newOwnerUuid);
|
|
661
|
+
}
|
|
662
|
+
})
|
|
663
|
+
.catch(function (error) {
|
|
664
|
+
_this.logger.error('ClientConnectionManager', "\u274C Authentication FAILED for " + addressStr + ":");
|
|
665
|
+
_this.logger.error('ClientConnectionManager', " - Error: " + error.message);
|
|
666
|
+
_this.logger.error('ClientConnectionManager', " - Used Stored Credentials: " + (storedCredentials ? 'YES' : 'NO'));
|
|
667
|
+
if (storedCredentials) {
|
|
668
|
+
_this.logger.error('ClientConnectionManager', " - Failed Credentials: uuid=" + storedCredentials.uuid + ", ownerUuid=" + storedCredentials.ownerUuid);
|
|
669
|
+
}
|
|
670
|
+
_this.logger.error('ClientConnectionManager', " - Current Client UUID: " + (clusterService.uuid || 'NOT SET'));
|
|
671
|
+
_this.logger.error('ClientConnectionManager', " - Current Client Owner UUID: " + (clusterService.ownerUuid || 'NOT SET'));
|
|
672
|
+
// Clear failed credentials
|
|
673
|
+
if (storedCredentials) {
|
|
674
|
+
_this.logger.info('ClientConnectionManager', "\uD83D\uDDD1\uFE0F Clearing failed credentials for " + addressStr);
|
|
675
|
+
_this.credentialPreservationService.clearCredentialsForAddress(address);
|
|
676
|
+
}
|
|
677
|
+
throw error;
|
|
678
|
+
});
|
|
473
679
|
};
|
|
474
680
|
return ClientConnectionManager;
|
|
475
681
|
}(events_1.EventEmitter));
|
|
@@ -6,6 +6,8 @@ import { ClientInfo } from '../ClientInfo';
|
|
|
6
6
|
import HazelcastClient from '../HazelcastClient';
|
|
7
7
|
import { MemberSelector } from '../core/MemberSelector';
|
|
8
8
|
import { MembershipListener } from '../core/MembershipListener';
|
|
9
|
+
import Address = require('../Address');
|
|
10
|
+
import { HazelcastFailoverManager } from './HazelcastFailoverManager';
|
|
9
11
|
export declare enum MemberEvent {
|
|
10
12
|
ADDED = 1,
|
|
11
13
|
REMOVED = 2,
|
|
@@ -35,6 +37,7 @@ export declare class ClusterService {
|
|
|
35
37
|
private readonly addressBlockDuration;
|
|
36
38
|
private reconnectionTask;
|
|
37
39
|
private readonly reconnectionInterval;
|
|
40
|
+
private failoverManager;
|
|
38
41
|
constructor(client: HazelcastClient);
|
|
39
42
|
/**
|
|
40
43
|
* Starts cluster service.
|
|
@@ -51,6 +54,14 @@ export declare class ClusterService {
|
|
|
51
54
|
* Returns the owner connection if available
|
|
52
55
|
*/
|
|
53
56
|
getOwnerConnection(): ClientConnection | null;
|
|
57
|
+
/**
|
|
58
|
+
* Returns whether failover is currently in progress
|
|
59
|
+
*/
|
|
60
|
+
isFailoverInProgress(): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Returns the list of known addresses in the cluster
|
|
63
|
+
*/
|
|
64
|
+
getKnownAddresses(): Address[];
|
|
54
65
|
/**
|
|
55
66
|
* Returns the list of members in the cluster.
|
|
56
67
|
* @returns
|
|
@@ -86,6 +97,14 @@ export declare class ClusterService {
|
|
|
86
97
|
private onConnectionClosed(connection);
|
|
87
98
|
private onHeartbeatStopped(connection);
|
|
88
99
|
private triggerFailover();
|
|
100
|
+
/**
|
|
101
|
+
* Handles single-node cluster reset - treats node restart as fresh cluster
|
|
102
|
+
*/
|
|
103
|
+
private handleSingleNodeClusterReset();
|
|
104
|
+
/**
|
|
105
|
+
* Handles multi-node failover - preserves existing logic
|
|
106
|
+
*/
|
|
107
|
+
private handleMultiNodeFailover();
|
|
89
108
|
/**
|
|
90
109
|
* Attempts emergency recovery when failover fails
|
|
91
110
|
*/
|
|
@@ -101,6 +120,23 @@ export declare class ClusterService {
|
|
|
101
120
|
private handleMemberAttributeChange(uuid, key, operationType, value);
|
|
102
121
|
private memberAdded(member);
|
|
103
122
|
private memberRemoved(member);
|
|
123
|
+
/**
|
|
124
|
+
* Performs comprehensive credential cleanup when cluster membership changes
|
|
125
|
+
* This ensures ALL credentials are consistent with the current cluster owner UUID
|
|
126
|
+
* @param connectionManager The connection manager instance
|
|
127
|
+
* @param currentClusterOwnerUuid The current cluster owner UUID
|
|
128
|
+
*/
|
|
129
|
+
/**
|
|
130
|
+
* Handles member added event - SERVER-FIRST APPROACH
|
|
131
|
+
* We trust what the server tells us and store it as credentials
|
|
132
|
+
* @param member The member that was added
|
|
133
|
+
*/
|
|
134
|
+
private handleMemberAdded(member);
|
|
135
|
+
/**
|
|
136
|
+
* Finds the current owner from the cluster state
|
|
137
|
+
* @returns The current owner member or null if not found
|
|
138
|
+
*/
|
|
139
|
+
private findCurrentOwner();
|
|
104
140
|
/**
|
|
105
141
|
* Logs the current state for debugging purposes
|
|
106
142
|
*/
|
|
@@ -134,5 +170,16 @@ export declare class ClusterService {
|
|
|
134
170
|
* @param blockDuration The duration to block the address (in milliseconds)
|
|
135
171
|
*/
|
|
136
172
|
private markAddressAsDownWithDuration(address, blockDuration);
|
|
173
|
+
/**
|
|
174
|
+
* Handles ownership change when failover occurs
|
|
175
|
+
* @param newOwnerAddress The address of the new owner
|
|
176
|
+
* @param newOwnerConnection The connection to the new owner
|
|
177
|
+
*/
|
|
178
|
+
handleOwnershipChange(newOwnerAddress: Address, newOwnerConnection: ClientConnection): void;
|
|
179
|
+
/**
|
|
180
|
+
* Gets the failover manager for external access
|
|
181
|
+
* @returns The failover manager instance
|
|
182
|
+
*/
|
|
183
|
+
getFailoverManager(): HazelcastFailoverManager;
|
|
137
184
|
shutdown(): void;
|
|
138
185
|
}
|