@celerispay/hazelcast-client 3.12.5 → 3.12.7
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/CHANGELOG.md +111 -87
- package/CHANGES_UNCOMMITTED.md +52 -0
- package/FAILOVER_FIXES.md +148 -230
- package/FAULT_TOLERANCE_IMPROVEMENTS.md +208 -0
- package/HAZELCAST_CLIENT_EVOLUTION.md +402 -0
- package/QUICK_START.md +184 -95
- package/RELEASE_SUMMARY.md +227 -147
- package/lib/HeartbeatService.js +11 -2
- package/lib/PartitionService.d.ts +14 -0
- package/lib/PartitionService.js +32 -9
- package/lib/invocation/ClientConnection.d.ts +14 -0
- package/lib/invocation/ClientConnection.js +95 -1
- package/lib/invocation/ClientConnectionManager.d.ts +95 -0
- package/lib/invocation/ClientConnectionManager.js +369 -7
- package/lib/invocation/ClusterService.d.ts +75 -5
- package/lib/invocation/ClusterService.js +430 -15
- package/lib/invocation/ConnectionAuthenticator.d.ts +11 -0
- package/lib/invocation/ConnectionAuthenticator.js +85 -12
- package/lib/invocation/CredentialPreservationService.d.ts +137 -0
- package/lib/invocation/CredentialPreservationService.js +369 -0
- package/lib/invocation/HazelcastFailoverManager.d.ts +102 -0
- package/lib/invocation/HazelcastFailoverManager.js +285 -0
- package/lib/invocation/InvocationService.js +8 -0
- package/lib/nearcache/StaleReadDetectorImpl.js +31 -4
- package/lib/proxy/ProxyManager.js +25 -4
- package/package.json +20 -28
|
@@ -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
|
/**
|
|
@@ -49,11 +50,17 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
49
50
|
_this.failedConnections = new Set();
|
|
50
51
|
_this.maxConnectionRetries = 3;
|
|
51
52
|
_this.connectionRetryDelay = 1000;
|
|
53
|
+
_this.connectionCleanupTask = null;
|
|
54
|
+
_this.connectionCleanupInterval = 15000; // 15 seconds between cleanup checks
|
|
55
|
+
_this.memberAddedEvents = new Set();
|
|
52
56
|
_this.client = client;
|
|
53
57
|
_this.logger = _this.client.getLoggingService().getLogger();
|
|
54
58
|
_this.addressTranslator = addressTranslator;
|
|
55
59
|
_this.addressProviders = addressProviders;
|
|
60
|
+
// Initialize credential preservation service for server data storage
|
|
61
|
+
_this.credentialPreservationService = new CredentialPreservationService_1.CredentialPreservationService(_this.logger);
|
|
56
62
|
_this.startConnectionHealthCheck();
|
|
63
|
+
_this.startConnectionCleanupTask();
|
|
57
64
|
return _this;
|
|
58
65
|
}
|
|
59
66
|
ClientConnectionManager.prototype.startConnectionHealthCheck = function () {
|
|
@@ -63,6 +70,50 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
63
70
|
_this.checkConnectionHealth();
|
|
64
71
|
}, 5000);
|
|
65
72
|
};
|
|
73
|
+
ClientConnectionManager.prototype.startConnectionCleanupTask = function () {
|
|
74
|
+
var _this = this;
|
|
75
|
+
// Periodically clean up stale connections and failed connection tracking
|
|
76
|
+
this.connectionCleanupTask = setInterval(function () {
|
|
77
|
+
_this.cleanupStaleConnections();
|
|
78
|
+
}, this.connectionCleanupInterval);
|
|
79
|
+
};
|
|
80
|
+
ClientConnectionManager.prototype.cleanupStaleConnections = function () {
|
|
81
|
+
var _this = this;
|
|
82
|
+
var now = Date.now();
|
|
83
|
+
var staleThreshold = 60000; // 1 minute threshold for stale connections
|
|
84
|
+
// Clean up failed connections that are older than threshold
|
|
85
|
+
var addressesToRemove = [];
|
|
86
|
+
this.failedConnections.forEach(function (address) {
|
|
87
|
+
// For now, we'll use a simple cleanup strategy
|
|
88
|
+
// In a more sophisticated implementation, we could track timestamps
|
|
89
|
+
addressesToRemove.push(address);
|
|
90
|
+
});
|
|
91
|
+
if (addressesToRemove.length > 0) {
|
|
92
|
+
this.logger.debug('ClientConnectionManager', "Cleaning up " + addressesToRemove.length + " stale failed connections");
|
|
93
|
+
addressesToRemove.forEach(function (address) {
|
|
94
|
+
_this.failedConnections.delete(address);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Clean up any connections that are not responding
|
|
98
|
+
Object.keys(this.establishedConnections).forEach(function (addressStr) {
|
|
99
|
+
var connection = _this.establishedConnections[addressStr];
|
|
100
|
+
if (connection && !connection.isAlive()) {
|
|
101
|
+
_this.logger.warn('ClientConnectionManager', "Cleaning up stale connection to " + addressStr);
|
|
102
|
+
_this.destroyConnection(connection.getAddress());
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
// Log current connection state
|
|
106
|
+
var activeConnections = Object.keys(this.establishedConnections).length;
|
|
107
|
+
var pendingConnections = Object.keys(this.pendingConnections).length;
|
|
108
|
+
var failedConnections = this.failedConnections.size;
|
|
109
|
+
this.logger.debug('ClientConnectionManager', "Connection State - Active: " + activeConnections + ", Pending: " + pendingConnections + ", Failed: " + failedConnections);
|
|
110
|
+
if (activeConnections > 0) {
|
|
111
|
+
Object.keys(this.establishedConnections).forEach(function (addressStr) {
|
|
112
|
+
var connection = _this.establishedConnections[addressStr];
|
|
113
|
+
_this.logger.debug('ClientConnectionManager', "Connection to " + addressStr + ": Alive=" + connection.isAlive() + ", Owner=" + connection.isAuthenticatedAsOwner());
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
66
117
|
ClientConnectionManager.prototype.checkConnectionHealth = function () {
|
|
67
118
|
// Use Object.keys() instead of Object.values() for compatibility
|
|
68
119
|
var connectionKeys = Object.keys(this.establishedConnections);
|
|
@@ -76,8 +127,8 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
76
127
|
}
|
|
77
128
|
};
|
|
78
129
|
ClientConnectionManager.prototype.isConnectionHealthy = function (connection) {
|
|
79
|
-
//
|
|
80
|
-
return connection.
|
|
130
|
+
// Use the improved health check method
|
|
131
|
+
return connection.isHealthy();
|
|
81
132
|
};
|
|
82
133
|
ClientConnectionManager.prototype.retryConnection = function (address, asOwner, retryCount) {
|
|
83
134
|
var _this = this;
|
|
@@ -86,11 +137,21 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
86
137
|
_this.failedConnections.delete(address.toString());
|
|
87
138
|
return connection;
|
|
88
139
|
}).catch(function (error) {
|
|
140
|
+
// Check if it's an authentication error
|
|
141
|
+
var isAuthError = error.message && (error.message.includes('Invalid Credentials') ||
|
|
142
|
+
error.message.includes('authentication') ||
|
|
143
|
+
error.message.includes('credentials'));
|
|
144
|
+
if (isAuthError) {
|
|
145
|
+
_this.logger.error('ClientConnectionManager', "Authentication failed for " + address.toString() + ", not retrying: " + error.message);
|
|
146
|
+
// Handle authentication errors specially
|
|
147
|
+
_this.handleAuthenticationError(address);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
89
150
|
if (retryCount < _this.maxConnectionRetries) {
|
|
90
151
|
_this.logger.warn('ClientConnectionManager', "Connection attempt " + (retryCount + 1) + " failed for " + address.toString() + ", retrying in " + _this.connectionRetryDelay + "ms");
|
|
91
|
-
return new Promise(function (resolve) {
|
|
152
|
+
return new Promise(function (resolve, reject) {
|
|
92
153
|
setTimeout(function () {
|
|
93
|
-
_this.retryConnection(address, asOwner, retryCount + 1).then(resolve).catch(
|
|
154
|
+
_this.retryConnection(address, asOwner, retryCount + 1).then(resolve).catch(reject);
|
|
94
155
|
}, _this.connectionRetryDelay);
|
|
95
156
|
});
|
|
96
157
|
}
|
|
@@ -124,6 +185,196 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
124
185
|
ClientConnectionManager.prototype.getActiveConnections = function () {
|
|
125
186
|
return this.establishedConnections;
|
|
126
187
|
};
|
|
188
|
+
/**
|
|
189
|
+
* Checks if we already have a connection to the given address
|
|
190
|
+
* @param address The address to check
|
|
191
|
+
* @returns true if connection exists and is healthy, false otherwise
|
|
192
|
+
*/
|
|
193
|
+
ClientConnectionManager.prototype.hasConnection = function (address) {
|
|
194
|
+
var addressStr = address.toString();
|
|
195
|
+
var connection = this.establishedConnections[addressStr];
|
|
196
|
+
return connection && connection.isAlive();
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Forces cleanup of all dead connections
|
|
200
|
+
* This is useful during failover to prevent connection leakage
|
|
201
|
+
*/
|
|
202
|
+
ClientConnectionManager.prototype.forceCleanupDeadConnections = function () {
|
|
203
|
+
var _this = this;
|
|
204
|
+
this.logger.info('ClientConnectionManager', 'Forcing cleanup of all dead connections');
|
|
205
|
+
var addressesToRemove = [];
|
|
206
|
+
// Check all established connections
|
|
207
|
+
Object.keys(this.establishedConnections).forEach(function (addressStr) {
|
|
208
|
+
var connection = _this.establishedConnections[addressStr];
|
|
209
|
+
if (connection && !connection.isHealthy()) {
|
|
210
|
+
_this.logger.warn('ClientConnectionManager', "Found dead connection to " + addressStr + ", marking for cleanup");
|
|
211
|
+
addressesToRemove.push(addressStr);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
// Remove dead connections
|
|
215
|
+
addressesToRemove.forEach(function (addressStr) {
|
|
216
|
+
var connection = _this.establishedConnections[addressStr];
|
|
217
|
+
if (connection) {
|
|
218
|
+
_this.destroyConnection(connection.getAddress());
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
// Also clean up any pending connections that might be stuck
|
|
222
|
+
Object.keys(this.pendingConnections).forEach(function (addressStr) {
|
|
223
|
+
var pendingConnection = _this.pendingConnections[addressStr];
|
|
224
|
+
if (pendingConnection) {
|
|
225
|
+
_this.logger.warn('ClientConnectionManager', "Cleaning up stuck pending connection to " + addressStr);
|
|
226
|
+
pendingConnection.reject(new Error('Connection cleanup forced'));
|
|
227
|
+
delete _this.pendingConnections[addressStr];
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
this.logger.info('ClientConnectionManager', "Cleanup completed. Removed " + addressesToRemove.length + " dead connections");
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Gets an existing connection to a specific address if it exists
|
|
234
|
+
* @param address The address to check for existing connection
|
|
235
|
+
* @returns The existing connection or undefined if none exists
|
|
236
|
+
*/
|
|
237
|
+
ClientConnectionManager.prototype.getConnection = function (address) {
|
|
238
|
+
var addressStr = address.toString();
|
|
239
|
+
return this.establishedConnections[addressStr];
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Gets all established connections
|
|
243
|
+
* @returns Object containing all established connections
|
|
244
|
+
*/
|
|
245
|
+
ClientConnectionManager.prototype.getEstablishedConnections = function () {
|
|
246
|
+
return this.establishedConnections;
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* Gets all pending connections
|
|
250
|
+
* @returns Object containing all pending connections
|
|
251
|
+
*/
|
|
252
|
+
ClientConnectionManager.prototype.getPendingConnections = function () {
|
|
253
|
+
return this.pendingConnections;
|
|
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
|
+
};
|
|
127
378
|
/**
|
|
128
379
|
* Returns the {@link ClientConnection} with given {@link Address}. If there is no such connection established,
|
|
129
380
|
* it first connects to the address and then return the {@link ClientConnection}.
|
|
@@ -159,6 +410,13 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
159
410
|
this.pendingConnections[addressIndex] = connectionResolver;
|
|
160
411
|
this.retryConnection(address, asOwner)
|
|
161
412
|
.then(function (clientConnection) {
|
|
413
|
+
// Safety check: ensure we got a proper ClientConnection
|
|
414
|
+
if (!clientConnection || typeof clientConnection.getAddress !== 'function') {
|
|
415
|
+
var error = new Error("Invalid connection object returned: " + typeof clientConnection);
|
|
416
|
+
_this.logger.error('ClientConnectionManager', error.message);
|
|
417
|
+
connectionResolver.reject(error);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
162
420
|
_this.establishedConnections[clientConnection.getAddress().toString()] = clientConnection;
|
|
163
421
|
_this.onConnectionOpened(clientConnection);
|
|
164
422
|
connectionResolver.resolve(clientConnection);
|
|
@@ -184,20 +442,67 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
184
442
|
*/
|
|
185
443
|
ClientConnectionManager.prototype.destroyConnection = function (address) {
|
|
186
444
|
var addressStr = address.toString();
|
|
445
|
+
// Clean up pending connections
|
|
187
446
|
if (this.pendingConnections.hasOwnProperty(addressStr)) {
|
|
188
|
-
this.pendingConnections[addressStr].reject(
|
|
447
|
+
this.pendingConnections[addressStr].reject(new Error('Connection destroyed'));
|
|
448
|
+
delete this.pendingConnections[addressStr];
|
|
189
449
|
}
|
|
450
|
+
// Clean up established connections
|
|
190
451
|
if (this.establishedConnections.hasOwnProperty(addressStr)) {
|
|
191
452
|
var conn = this.establishedConnections[addressStr];
|
|
192
453
|
delete this.establishedConnections[addressStr];
|
|
193
|
-
|
|
454
|
+
try {
|
|
455
|
+
conn.close();
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
this.logger.warn('ClientConnectionManager', "Error closing connection to " + addressStr + ":", error);
|
|
459
|
+
}
|
|
194
460
|
this.onConnectionClosed(conn);
|
|
195
461
|
}
|
|
462
|
+
// Mark as failed to prevent immediate reconnection attempts
|
|
463
|
+
this.failedConnections.add(addressStr);
|
|
464
|
+
this.logger.debug('ClientConnectionManager', "Connection to " + addressStr + " destroyed and marked as failed");
|
|
465
|
+
};
|
|
466
|
+
/**
|
|
467
|
+
* Cleans up all connections to a specific address during failover
|
|
468
|
+
* @param address The address to cleanup
|
|
469
|
+
*/
|
|
470
|
+
ClientConnectionManager.prototype.cleanupConnectionsForFailover = function (address) {
|
|
471
|
+
var addressStr = address.toString();
|
|
472
|
+
this.logger.info('ClientConnectionManager', "Cleaning up all connections to " + addressStr + " for failover");
|
|
473
|
+
// Force cleanup of all connection types
|
|
474
|
+
this.destroyConnection(address);
|
|
475
|
+
// Remove from failed connections to allow reconnection after failover
|
|
476
|
+
this.failedConnections.delete(addressStr);
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* Handles authentication errors by clearing failed connections and allowing retry
|
|
480
|
+
* @param address The address that had authentication issues
|
|
481
|
+
*/
|
|
482
|
+
ClientConnectionManager.prototype.handleAuthenticationError = function (address) {
|
|
483
|
+
var addressStr = address.toString();
|
|
484
|
+
this.logger.warn('ClientConnectionManager', "Handling authentication error for " + addressStr);
|
|
485
|
+
// Clear from failed connections to allow retry with fresh credentials
|
|
486
|
+
this.failedConnections.delete(addressStr);
|
|
487
|
+
// Also clear any established connections to this address
|
|
488
|
+
if (this.establishedConnections.hasOwnProperty(addressStr)) {
|
|
489
|
+
this.logger.info('ClientConnectionManager', "Clearing established connection to " + addressStr + " due to auth error");
|
|
490
|
+
this.destroyConnection(address);
|
|
491
|
+
}
|
|
492
|
+
// Clear any pending connections
|
|
493
|
+
if (this.pendingConnections.hasOwnProperty(addressStr)) {
|
|
494
|
+
this.logger.info('ClientConnectionManager', "Clearing pending connection to " + addressStr + " due to auth error");
|
|
495
|
+
this.pendingConnections[addressStr].reject(new Error('Authentication error, connection cleared'));
|
|
496
|
+
delete this.pendingConnections[addressStr];
|
|
497
|
+
}
|
|
196
498
|
};
|
|
197
499
|
ClientConnectionManager.prototype.shutdown = function () {
|
|
198
500
|
if (this.connectionHealthCheckInterval) {
|
|
199
501
|
clearInterval(this.connectionHealthCheckInterval);
|
|
200
502
|
}
|
|
503
|
+
if (this.connectionCleanupTask) {
|
|
504
|
+
clearInterval(this.connectionCleanupTask);
|
|
505
|
+
}
|
|
201
506
|
for (var pending in this.pendingConnections) {
|
|
202
507
|
this.pendingConnections[pending].reject(new HazelcastError_1.ClientNotActiveError('Client is shutting down!'));
|
|
203
508
|
}
|
|
@@ -290,8 +595,65 @@ var ClientConnectionManager = /** @class */ (function (_super) {
|
|
|
290
595
|
this.emit(EMIT_CONNECTION_OPENED, connection);
|
|
291
596
|
};
|
|
292
597
|
ClientConnectionManager.prototype.authenticate = function (connection, ownerConnection) {
|
|
598
|
+
var _this = this;
|
|
599
|
+
var address = connection.getAddress();
|
|
600
|
+
var addressStr = address.toString();
|
|
601
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDD10 Starting authentication for " + addressStr + " (owner=" + ownerConnection + ")");
|
|
602
|
+
// Check if we have stored credentials for this address
|
|
603
|
+
var storedCredentials = this.credentialPreservationService.restoreCredentials(address);
|
|
604
|
+
var hasMemberAddedEvent = this.hasMemberAddedEvent(address);
|
|
605
|
+
if (storedCredentials) {
|
|
606
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDCCB Using STORED credentials for " + addressStr + ":");
|
|
607
|
+
this.logger.info('ClientConnectionManager', " - UUID: " + storedCredentials.uuid);
|
|
608
|
+
this.logger.info('ClientConnectionManager', " - Owner UUID: " + storedCredentials.ownerUuid);
|
|
609
|
+
this.logger.info('ClientConnectionManager', " - Group Name: " + storedCredentials.groupName);
|
|
610
|
+
this.logger.info('ClientConnectionManager', " - Member Added Event: " + (hasMemberAddedEvent ? 'YES' : 'NO'));
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDCCB No stored credentials found for " + addressStr + ", using fresh authentication");
|
|
614
|
+
}
|
|
615
|
+
// Log current cluster state for debugging
|
|
616
|
+
var clusterService = this.client.getClusterService();
|
|
617
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDD0D Current cluster state for " + addressStr + ":");
|
|
618
|
+
this.logger.info('ClientConnectionManager', " - Client UUID: " + (clusterService.uuid || 'NOT SET'));
|
|
619
|
+
this.logger.info('ClientConnectionManager', " - Client Owner UUID: " + (clusterService.ownerUuid || 'NOT SET'));
|
|
620
|
+
this.logger.info('ClientConnectionManager', " - Active Members: " + clusterService.getMembers().length);
|
|
621
|
+
// Log what we're about to send
|
|
622
|
+
this.logger.info('ClientConnectionManager', "\uD83D\uDCE4 Sending authentication request to " + addressStr + " with:");
|
|
623
|
+
this.logger.info('ClientConnectionManager', " - Owner Connection: " + ownerConnection);
|
|
624
|
+
this.logger.info('ClientConnectionManager', " - Using Stored Credentials: " + (storedCredentials ? 'YES' : 'NO'));
|
|
293
625
|
var authenticator = new ConnectionAuthenticator_1.ConnectionAuthenticator(connection, this.client);
|
|
294
|
-
|
|
626
|
+
// Use normal authentication flow - server handles everything
|
|
627
|
+
return authenticator.authenticate(ownerConnection)
|
|
628
|
+
.then(function () {
|
|
629
|
+
_this.logger.info('ClientConnectionManager', "\u2705 Authentication successful for " + addressStr);
|
|
630
|
+
// After successful authentication, store the new credentials from server
|
|
631
|
+
if (ownerConnection) {
|
|
632
|
+
var newUuid = clusterService.uuid;
|
|
633
|
+
var newOwnerUuid = clusterService.ownerUuid;
|
|
634
|
+
_this.logger.info('ClientConnectionManager', "\uD83D\uDCBE Storing new credentials from server for " + addressStr + ":");
|
|
635
|
+
_this.logger.info('ClientConnectionManager', " - New UUID: " + newUuid);
|
|
636
|
+
_this.logger.info('ClientConnectionManager', " - New Owner UUID: " + newOwnerUuid);
|
|
637
|
+
// Store the new credentials
|
|
638
|
+
_this.credentialPreservationService.updateCredentials(address, newUuid, newOwnerUuid);
|
|
639
|
+
}
|
|
640
|
+
})
|
|
641
|
+
.catch(function (error) {
|
|
642
|
+
_this.logger.error('ClientConnectionManager', "\u274C Authentication FAILED for " + addressStr + ":");
|
|
643
|
+
_this.logger.error('ClientConnectionManager', " - Error: " + error.message);
|
|
644
|
+
_this.logger.error('ClientConnectionManager', " - Used Stored Credentials: " + (storedCredentials ? 'YES' : 'NO'));
|
|
645
|
+
if (storedCredentials) {
|
|
646
|
+
_this.logger.error('ClientConnectionManager', " - Failed Credentials: uuid=" + storedCredentials.uuid + ", ownerUuid=" + storedCredentials.ownerUuid);
|
|
647
|
+
}
|
|
648
|
+
_this.logger.error('ClientConnectionManager', " - Current Client UUID: " + (clusterService.uuid || 'NOT SET'));
|
|
649
|
+
_this.logger.error('ClientConnectionManager', " - Current Client Owner UUID: " + (clusterService.ownerUuid || 'NOT SET'));
|
|
650
|
+
// Clear failed credentials
|
|
651
|
+
if (storedCredentials) {
|
|
652
|
+
_this.logger.info('ClientConnectionManager', "\uD83D\uDDD1\uFE0F Clearing failed credentials for " + addressStr);
|
|
653
|
+
_this.credentialPreservationService.clearCredentialsForAddress(address);
|
|
654
|
+
}
|
|
655
|
+
throw error;
|
|
656
|
+
});
|
|
295
657
|
};
|
|
296
658
|
return ClientConnectionManager;
|
|
297
659
|
}(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,
|
|
@@ -33,6 +35,9 @@ export declare class ClusterService {
|
|
|
33
35
|
private readonly failoverCooldown;
|
|
34
36
|
private downAddresses;
|
|
35
37
|
private readonly addressBlockDuration;
|
|
38
|
+
private reconnectionTask;
|
|
39
|
+
private readonly reconnectionInterval;
|
|
40
|
+
private failoverManager;
|
|
36
41
|
constructor(client: HazelcastClient);
|
|
37
42
|
/**
|
|
38
43
|
* Starts cluster service.
|
|
@@ -45,6 +50,10 @@ export declare class ClusterService {
|
|
|
45
50
|
*/
|
|
46
51
|
connectToCluster(): Promise<void>;
|
|
47
52
|
getPossibleMemberAddresses(): Promise<string[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Returns the owner connection if available
|
|
55
|
+
*/
|
|
56
|
+
getOwnerConnection(): ClientConnection | null;
|
|
48
57
|
/**
|
|
49
58
|
* Returns the list of members in the cluster.
|
|
50
59
|
* @returns
|
|
@@ -61,11 +70,6 @@ export declare class ClusterService {
|
|
|
61
70
|
* @returns {ClientInfo}
|
|
62
71
|
*/
|
|
63
72
|
getClientInfo(): ClientInfo;
|
|
64
|
-
/**
|
|
65
|
-
* Returns the connection associated with owner node of this client.
|
|
66
|
-
* @returns {ClientConnection}
|
|
67
|
-
*/
|
|
68
|
-
getOwnerConnection(): ClientConnection;
|
|
69
73
|
/**
|
|
70
74
|
* Adds MembershipListener to listen for membership updates. There is no check for duplicate registrations,
|
|
71
75
|
* so if you register the listener twice, it will get events twice.
|
|
@@ -85,6 +89,10 @@ export declare class ClusterService {
|
|
|
85
89
|
private onConnectionClosed(connection);
|
|
86
90
|
private onHeartbeatStopped(connection);
|
|
87
91
|
private triggerFailover();
|
|
92
|
+
/**
|
|
93
|
+
* Attempts emergency recovery when failover fails
|
|
94
|
+
*/
|
|
95
|
+
private attemptEmergencyRecovery();
|
|
88
96
|
private isAddressKnownDown(address);
|
|
89
97
|
private markAddressAsDown(address);
|
|
90
98
|
private getDownAddressesInfo();
|
|
@@ -96,4 +104,66 @@ export declare class ClusterService {
|
|
|
96
104
|
private handleMemberAttributeChange(uuid, key, operationType, value);
|
|
97
105
|
private memberAdded(member);
|
|
98
106
|
private memberRemoved(member);
|
|
107
|
+
/**
|
|
108
|
+
* Performs comprehensive credential cleanup when cluster membership changes
|
|
109
|
+
* This ensures ALL credentials are consistent with the current cluster owner UUID
|
|
110
|
+
* @param connectionManager The connection manager instance
|
|
111
|
+
* @param currentClusterOwnerUuid The current cluster owner UUID
|
|
112
|
+
*/
|
|
113
|
+
/**
|
|
114
|
+
* Handles member added event - SERVER-FIRST APPROACH
|
|
115
|
+
* We trust what the server tells us and store it as credentials
|
|
116
|
+
* @param member The member that was added
|
|
117
|
+
*/
|
|
118
|
+
private handleMemberAdded(member);
|
|
119
|
+
/**
|
|
120
|
+
* Finds the current owner from the cluster state
|
|
121
|
+
* @returns The current owner member or null if not found
|
|
122
|
+
*/
|
|
123
|
+
private findCurrentOwner();
|
|
124
|
+
/**
|
|
125
|
+
* Logs the current state for debugging purposes
|
|
126
|
+
*/
|
|
127
|
+
private logCurrentState();
|
|
128
|
+
private startReconnectionTask();
|
|
129
|
+
/**
|
|
130
|
+
* Starts a periodic task to log the current state for debugging
|
|
131
|
+
*/
|
|
132
|
+
private startStateLoggingTask();
|
|
133
|
+
private attemptReconnectionToFailedNodes();
|
|
134
|
+
/**
|
|
135
|
+
* Attempts to establish a connection to a specific address
|
|
136
|
+
* @param address The address to reconnect to
|
|
137
|
+
*/
|
|
138
|
+
private attemptReconnectionToAddress(address);
|
|
139
|
+
/**
|
|
140
|
+
* Evaluates whether we should switch ownership to a reconnected node
|
|
141
|
+
* @param address The address of the reconnected node
|
|
142
|
+
* @param connection The connection to the reconnected node
|
|
143
|
+
*/
|
|
144
|
+
private evaluateOwnershipChange(address, connection);
|
|
145
|
+
/**
|
|
146
|
+
* Promotes a connection to owner status
|
|
147
|
+
* @param connection The connection to promote
|
|
148
|
+
* @param address The address of the promoted connection
|
|
149
|
+
*/
|
|
150
|
+
private promoteToOwner(connection, address);
|
|
151
|
+
/**
|
|
152
|
+
* Marks an address as down with a custom block duration
|
|
153
|
+
* @param address The address to mark as down
|
|
154
|
+
* @param blockDuration The duration to block the address (in milliseconds)
|
|
155
|
+
*/
|
|
156
|
+
private markAddressAsDownWithDuration(address, blockDuration);
|
|
157
|
+
/**
|
|
158
|
+
* Handles ownership change when failover occurs
|
|
159
|
+
* @param newOwnerAddress The address of the new owner
|
|
160
|
+
* @param newOwnerConnection The connection to the new owner
|
|
161
|
+
*/
|
|
162
|
+
handleOwnershipChange(newOwnerAddress: Address, newOwnerConnection: ClientConnection): void;
|
|
163
|
+
/**
|
|
164
|
+
* Gets the failover manager for external access
|
|
165
|
+
* @returns The failover manager instance
|
|
166
|
+
*/
|
|
167
|
+
getFailoverManager(): HazelcastFailoverManager;
|
|
168
|
+
shutdown(): void;
|
|
99
169
|
}
|