Node JS version
This commit is contained in:
658
node_modules/cassandra-driver/lib/host.js
generated
vendored
Normal file
658
node_modules/cassandra-driver/lib/host.js
generated
vendored
Normal file
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
* Copyright DataStax, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const events = require('events');
|
||||
|
||||
const utils = require('./utils');
|
||||
const types = require('./types');
|
||||
const HostConnectionPool = require('./host-connection-pool');
|
||||
const PrepareHandler = require('./prepare-handler');
|
||||
const promiseUtils = require('./promise-utils');
|
||||
|
||||
const healthResponseCountInterval = 200;
|
||||
|
||||
/**
|
||||
* Represents a Cassandra node.
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class Host extends events.EventEmitter {
|
||||
|
||||
/**
|
||||
* Creates a new Host instance.
|
||||
*/
|
||||
constructor(address, protocolVersion, options, metadata) {
|
||||
super();
|
||||
/**
|
||||
* Gets ip address and port number of the node separated by `:`.
|
||||
* @type {String}
|
||||
*/
|
||||
this.address = address;
|
||||
this.setDownAt = 0;
|
||||
this.log = utils.log;
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the moment when the Host was marked as UP.
|
||||
* @type {Number|null}
|
||||
* @ignore
|
||||
* @internal
|
||||
*/
|
||||
this.isUpSince = null;
|
||||
Object.defineProperty(this, 'options', { value: options, enumerable: false, writable: false });
|
||||
|
||||
/**
|
||||
* The host pool.
|
||||
* @internal
|
||||
* @ignore
|
||||
* @type {HostConnectionPool}
|
||||
*/
|
||||
Object.defineProperty(this, 'pool', { value: new HostConnectionPool(this, protocolVersion), enumerable: false });
|
||||
|
||||
this.pool.on('open', err => promiseUtils.toBackground(this._onNewConnectionOpen(err)));
|
||||
this.pool.on('remove', () => this._checkPoolState());
|
||||
|
||||
/**
|
||||
* Gets string containing the Cassandra version.
|
||||
* @type {String}
|
||||
*/
|
||||
this.cassandraVersion = null;
|
||||
|
||||
/**
|
||||
* Gets data center name of the node.
|
||||
* @type {String}
|
||||
*/
|
||||
this.datacenter = null;
|
||||
|
||||
/**
|
||||
* Gets rack name of the node.
|
||||
* @type {String}
|
||||
*/
|
||||
this.rack = null;
|
||||
|
||||
/**
|
||||
* Gets the tokens assigned to the node.
|
||||
* @type {Array}
|
||||
*/
|
||||
this.tokens = null;
|
||||
|
||||
/**
|
||||
* Gets the id of the host.
|
||||
* <p>This identifier is used by the server for internal communication / gossip.</p>
|
||||
* @type {Uuid}
|
||||
*/
|
||||
this.hostId = null;
|
||||
|
||||
/**
|
||||
* Gets string containing the DSE version or null if not set.
|
||||
* @type {String}
|
||||
*/
|
||||
this.dseVersion = null;
|
||||
|
||||
/**
|
||||
* Gets the DSE Workloads the host is running.
|
||||
* <p>
|
||||
* This is based on the "workload" or "workloads" columns in {@code system.local} and {@code system.peers}.
|
||||
* <p/>
|
||||
* <p>
|
||||
* Workload labels may vary depending on the DSE version in use;e.g. DSE 5.1 may report two distinct workloads:
|
||||
* <code>Search</code> and <code>Analytics</code>, while DSE 5.0 would report a single
|
||||
* <code>SearchAnalytics</code> workload instead. The driver simply returns the workload labels as reported by
|
||||
* DSE, without any form of pre-processing.
|
||||
* <p/>
|
||||
* <p>When the information is unavailable, this property returns an empty array.</p>
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.workloads = utils.emptyArray;
|
||||
|
||||
// the distance as last set using the load balancing policy
|
||||
this._distance = types.distance.ignored;
|
||||
this._healthResponseCounter = 0;
|
||||
|
||||
// Make some of the private instance variables not enumerable to prevent from showing when inspecting
|
||||
Object.defineProperty(this, '_metadata', { value: metadata, enumerable: false });
|
||||
Object.defineProperty(this, '_healthResponseCountTimer', { value: null, enumerable: false, writable: true });
|
||||
|
||||
this.reconnectionSchedule = this.options.policies.reconnection.newSchedule();
|
||||
this.reconnectionDelay = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this host as not available for query coordination, when the host was previously marked as UP, otherwise its
|
||||
* a no-op.
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
setDown() {
|
||||
// Multiple events signaling that a host is failing could cause multiple calls to this method
|
||||
if (this.setDownAt !== 0) {
|
||||
// the host is already marked as Down
|
||||
return;
|
||||
}
|
||||
if (this.pool.isClosing()) {
|
||||
// the pool is being closed/shutdown, don't mind
|
||||
return;
|
||||
}
|
||||
this.setDownAt = Date.now();
|
||||
if (this.pool.coreConnectionsLength > 0) {
|
||||
// According to the distance, there should be connections open to it => issue a warning
|
||||
this.log('warning', `Host ${this.address} considered as DOWN. Reconnection delay ${this.reconnectionDelay}ms.`);
|
||||
}
|
||||
else {
|
||||
this.log('info', `Host ${this.address} considered as DOWN.`);
|
||||
}
|
||||
this.emit('down');
|
||||
this._checkPoolState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this host as available for querying.
|
||||
* @param {Boolean} [clearReconnection]
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
setUp(clearReconnection) {
|
||||
if (!this.setDownAt) {
|
||||
//The host is already marked as UP
|
||||
return;
|
||||
}
|
||||
this.log('info', `Setting host ${this.address} as UP`);
|
||||
this.setDownAt = 0;
|
||||
this.isUpSince = Date.now();
|
||||
//if it was unhealthy and now it is not, lets reset the reconnection schedule.
|
||||
this.reconnectionSchedule = this.options.policies.reconnection.newSchedule();
|
||||
if (clearReconnection) {
|
||||
this.pool.clearNewConnectionAttempt();
|
||||
}
|
||||
this.emit('up');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the reconnectionSchedule and tries to issue a reconnection immediately.
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
checkIsUp() {
|
||||
if (this.isUp()) {
|
||||
return;
|
||||
}
|
||||
this.reconnectionSchedule = this.options.policies.reconnection.newSchedule();
|
||||
this.reconnectionDelay = 0;
|
||||
this.pool.attemptNewConnectionImmediate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Boolean} [waitForPending] When true, it waits for in-flight operations to be finish before closing the
|
||||
* connections.
|
||||
* @returns {Promise<void>}
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
shutdown(waitForPending) {
|
||||
if (this._healthResponseCountTimer) {
|
||||
clearInterval(this._healthResponseCountTimer);
|
||||
}
|
||||
if (waitForPending) {
|
||||
this.pool.drainAndShutdown();
|
||||
// Gracefully draining and shutting down the pool is being done in the background
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.pool.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the node is UP now (seen as UP by the driver).
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isUp() {
|
||||
return !this.setDownAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the host can be considered as UP.
|
||||
* Deprecated: Use {@link Host#isUp()} instead.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canBeConsideredAsUp() {
|
||||
const self = this;
|
||||
function hasTimePassed() {
|
||||
return new Date().getTime() - self.setDownAt >= self.reconnectionDelay;
|
||||
}
|
||||
return !this.setDownAt || hasTimePassed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the distance of the host relative to the client using the load balancing policy.
|
||||
* @param {Number} distance
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
setDistance(distance) {
|
||||
const previousDistance = this._distance;
|
||||
this._distance = distance || types.distance.local;
|
||||
if (this.options.pooling.coreConnectionsPerHost) {
|
||||
this.pool.coreConnectionsLength = this.options.pooling.coreConnectionsPerHost[this._distance] || 0;
|
||||
}
|
||||
else {
|
||||
this.pool.coreConnectionsLength = 1;
|
||||
}
|
||||
if (this._distance === previousDistance) {
|
||||
return this._distance;
|
||||
}
|
||||
if (this._healthResponseCountTimer) {
|
||||
clearInterval(this._healthResponseCountTimer);
|
||||
}
|
||||
if (this._distance === types.distance.ignored) {
|
||||
// this host was local/remote and now must be ignored
|
||||
this.emit('ignore');
|
||||
this.pool.drainAndShutdown();
|
||||
}
|
||||
else {
|
||||
if (!this.isUp()) {
|
||||
this.checkIsUp();
|
||||
}
|
||||
// Reset the health check timer
|
||||
this._healthResponseCountTimer = setInterval(() => {
|
||||
this._healthResponseCounter = this.pool.getAndResetResponseCounter();
|
||||
}, healthResponseCountInterval);
|
||||
}
|
||||
return this._distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the protocol version of a given host
|
||||
* @param {Number} value
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
setProtocolVersion(value) {
|
||||
this.pool.protocolVersion = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the least busy connection from the pool.
|
||||
* @param {Connection} [previousConnection] When provided, the pool should attempt to obtain a different connection.
|
||||
* @returns {Connection!}
|
||||
* @throws {Error}
|
||||
* @throws {BusyConnectionError}
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
borrowConnection(previousConnection) {
|
||||
return this.pool.borrowConnection(previousConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all the connection in the pool.
|
||||
* @param {string} keyspace
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
warmupPool(keyspace) {
|
||||
return this.pool.warmup(keyspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts creating the pool in the background.
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
initializePool() {
|
||||
this.pool.increaseSize();
|
||||
}
|
||||
/**
|
||||
* Gets any connection that is already opened or null if not found.
|
||||
* @returns {Connection}
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
getActiveConnection() {
|
||||
if (!this.isUp() || !this.pool.connections.length) {
|
||||
return null;
|
||||
}
|
||||
return this.pool.connections[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to get the amount of responses dequeued in the last interval (between 200ms and 400ms) on all
|
||||
* connections to the host.
|
||||
* @returns {Number}
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
getResponseCount() {
|
||||
// Last interval plus the current count
|
||||
return this._healthResponseCounter + this.pool.responseCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the health of a connection in the pool
|
||||
* @param {Connection} connection
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
checkHealth(connection) {
|
||||
if (connection.timedOutOperations <= this.options.socketOptions.defunctReadTimeoutThreshold) {
|
||||
return;
|
||||
}
|
||||
this.removeFromPool(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Connection} connection
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
removeFromPool(connection) {
|
||||
this.pool.remove(connection);
|
||||
this._checkPoolState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method that gets the amount of in-flight requests on all connections to the host.
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
getInFlight() {
|
||||
return this.pool.getInFlight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the internal state of the connection pool.
|
||||
* If the pool size is smaller than expected, schedule a new connection attempt.
|
||||
* If the amount of connections is 0 for not ignored hosts, the host must be down.
|
||||
* @private
|
||||
*/
|
||||
_checkPoolState() {
|
||||
if (this.pool.isClosing()) {
|
||||
return;
|
||||
}
|
||||
if (this.pool.connections.length < this.pool.coreConnectionsLength) {
|
||||
// the pool needs to grow / reconnect
|
||||
if (!this.pool.hasScheduledNewConnection()) {
|
||||
this.reconnectionDelay = this.reconnectionSchedule.next().value;
|
||||
this.pool.scheduleNewConnectionAttempt(this.reconnectionDelay);
|
||||
}
|
||||
}
|
||||
const shouldHaveConnections = this._distance !== types.distance.ignored && this.pool.coreConnectionsLength > 0;
|
||||
if (shouldHaveConnections && this.pool.connections.length === 0) {
|
||||
// Mark as DOWN, if its UP
|
||||
this.setDown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed after an scheduled new connection attempt finished
|
||||
* @private
|
||||
*/
|
||||
async _onNewConnectionOpen(err) {
|
||||
if (err) {
|
||||
this._checkPoolState();
|
||||
return;
|
||||
}
|
||||
if (!this.isUp() && this.options.rePrepareOnUp) {
|
||||
this.log('info', `Re-preparing all queries on host ${this.address} before setting it as UP`);
|
||||
const allPrepared = this._metadata.getAllPrepared();
|
||||
try {
|
||||
await PrepareHandler.prepareAllQueries(this, allPrepared);
|
||||
}
|
||||
catch (err) {
|
||||
this.log('warning', `Failed re-preparing on host ${this.address}: ${err}`, err);
|
||||
}
|
||||
}
|
||||
this.setUp();
|
||||
this.pool.increaseSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the Cassandra Version as an Array of Numbers having the major version in the first
|
||||
* position.
|
||||
* @returns {Array.<Number>}
|
||||
*/
|
||||
getCassandraVersion() {
|
||||
if (!this.cassandraVersion) {
|
||||
return utils.emptyArray;
|
||||
}
|
||||
return this.cassandraVersion.split('-')[0].split('.').map(x => parseInt(x, 10));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DSE version of the host as an Array, containing the major version in the first position.
|
||||
* In case the cluster is not a DSE cluster, it returns an empty Array.
|
||||
* @returns {Array}
|
||||
*/
|
||||
getDseVersion() {
|
||||
if (!this.dseVersion) {
|
||||
return utils.emptyArray;
|
||||
}
|
||||
return this.dseVersion.split('-')[0].split('.').map(x => parseInt(x, 10));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an associative-array of {@link Host hosts} that can be iterated.
|
||||
* It creates an internal copy when adding or removing, making it safe to iterate using the values()
|
||||
* method within async operations.
|
||||
* @extends events.EventEmitter
|
||||
* @constructor
|
||||
*/
|
||||
class HostMap extends events.EventEmitter{
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._items = new Map();
|
||||
this._values = null;
|
||||
|
||||
Object.defineProperty(this, 'length', { get: () => this.values().length, enumerable: true });
|
||||
|
||||
/**
|
||||
* Emitted when a host is added to the map
|
||||
* @event HostMap#add
|
||||
*/
|
||||
/**
|
||||
* Emitted when a host is removed from the map
|
||||
* @event HostMap#remove
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a provided function once per map element.
|
||||
* @param callback
|
||||
*/
|
||||
forEach(callback) {
|
||||
const items = this._items;
|
||||
for (const [ key, value ] of items) {
|
||||
callback(value, key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link Host host} by key or undefined if not found.
|
||||
* @param {String} key
|
||||
* @returns {Host}
|
||||
*/
|
||||
get(key) {
|
||||
return this._items.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of host addresses.
|
||||
* @returns {Array.<String>}
|
||||
*/
|
||||
keys() {
|
||||
return Array.from(this._items.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the map.
|
||||
* @param {String} key The key of the host
|
||||
* @fires HostMap#remove
|
||||
*/
|
||||
remove(key) {
|
||||
const value = this._items.get(key);
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear cache
|
||||
this._values = null;
|
||||
|
||||
// Copy the values
|
||||
const copy = new Map(this._items);
|
||||
copy.delete(key);
|
||||
|
||||
this._items = copy;
|
||||
this.emit('remove', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes multiple hosts from the map.
|
||||
* @param {Array.<String>} keys
|
||||
* @fires HostMap#remove
|
||||
*/
|
||||
removeMultiple(keys) {
|
||||
// Clear value cache
|
||||
this._values = null;
|
||||
|
||||
// Copy the values
|
||||
const copy = new Map(this._items);
|
||||
const removedHosts = [];
|
||||
|
||||
for (const key of keys) {
|
||||
const h = copy.get(key);
|
||||
|
||||
if (!h) {
|
||||
continue;
|
||||
}
|
||||
|
||||
removedHosts.push(h);
|
||||
copy.delete(key);
|
||||
}
|
||||
|
||||
this._items = copy;
|
||||
removedHosts.forEach(h => this.emit('remove', h));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new item to the map.
|
||||
* @param {String} key The key of the host
|
||||
* @param {Host} value The host to be added
|
||||
* @fires HostMap#remove
|
||||
* @fires HostMap#add
|
||||
*/
|
||||
set(key, value) {
|
||||
// Clear values cache
|
||||
this._values = null;
|
||||
|
||||
const originalValue = this._items.get(key);
|
||||
if (originalValue) {
|
||||
//The internal structure does not change
|
||||
this._items.set(key, value);
|
||||
//emit a remove followed by a add
|
||||
this.emit('remove', originalValue);
|
||||
this.emit('add', value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy the values
|
||||
const copy = new Map(this._items);
|
||||
copy.set(key, value);
|
||||
this._items = copy;
|
||||
this.emit('add', value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shallow copy of a portion of the items into a new array object.
|
||||
* Backward-compatibility.
|
||||
* @param {Number} [begin]
|
||||
* @param {Number} [end]
|
||||
* @returns {Array}
|
||||
* @ignore
|
||||
*/
|
||||
slice(begin, end) {
|
||||
if (!begin && !end) {
|
||||
// Avoid making a copy of the copy
|
||||
return this.values();
|
||||
}
|
||||
|
||||
return this.values().slice(begin || 0, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: Use set() instead.
|
||||
* @ignore
|
||||
* @deprecated
|
||||
*/
|
||||
push(k, v) {
|
||||
this.set(k, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shallow copy of the values of the map.
|
||||
* @returns {Array.<Host>}
|
||||
*/
|
||||
values() {
|
||||
if (!this._values) {
|
||||
// Cache the values
|
||||
this._values = Object.freeze(Array.from(this._items.values()));
|
||||
}
|
||||
|
||||
return this._values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the map.
|
||||
* @returns {Array.<Host>} The previous items
|
||||
*/
|
||||
clear() {
|
||||
const previousItems = this.values();
|
||||
|
||||
// Clear cache
|
||||
this._values = null;
|
||||
|
||||
// Clear items
|
||||
this._items = new Map();
|
||||
|
||||
// Emit events
|
||||
previousItems.forEach(h => this.emit('remove', h));
|
||||
|
||||
return previousItems;
|
||||
}
|
||||
|
||||
inspect() {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
// Node.js 10 and below don't support Object.fromEntries()
|
||||
if (Object.fromEntries) {
|
||||
return Object.fromEntries(this._items);
|
||||
}
|
||||
|
||||
const obj = {};
|
||||
for (const [ key, value ] of this._items) {
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Host,
|
||||
HostMap
|
||||
};
|
Reference in New Issue
Block a user