Node JS version

This commit is contained in:
Aravind142857
2023-06-09 20:08:47 -05:00
parent 8983f0dd80
commit a8b8883b11
894 changed files with 152408 additions and 73 deletions

View File

@@ -0,0 +1,139 @@
/*
* 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 dns = require('dns');
const util = require('util');
const utils = require('../utils');
/** @module policies/addressResolution */
/**
* @class
* @classdesc
* Translates IP addresses received from Cassandra nodes into locally queryable
* addresses.
* <p>
* The driver auto-detects new Cassandra nodes added to the cluster through server
* side pushed notifications and through checking the system tables. For each
* node, the address received will correspond to the address set as
* <code>rpc_address</code> in the node yaml file. In most case, this is the correct
* address to use by the driver and that is what is used by default. However,
* sometimes the addresses received through this mechanism will either not be
* reachable directly by the driver or should not be the preferred address to use
* to reach the node (for instance, the <code>rpc_address</code> set on Cassandra nodes
* might be a private IP, but some clients may have to use a public IP, or
* pass by a router to reach that node). This interface allows to deal with
* such cases, by allowing to translate an address as sent by a Cassandra node
* to another address to be used by the driver for connection.
* <p>
* Please note that the contact points addresses provided while creating the
* {@link Client} instance are not "translated", only IP address retrieve from or sent
* by Cassandra nodes to the driver are.
* @constructor
*/
function AddressTranslator() {
}
/**
* Translates a Cassandra <code>rpc_address</code> to another address if necessary.
* @param {String} address the address of a node as returned by Cassandra.
* <p>
* Note that if the <code>rpc_address</code> of a node has been configured to <code>0.0.0.0</code>
* server side, then the provided address will be the node <code>listen_address</code>,
* *not* <code>0.0.0.0</code>.
* </p>
* @param {Number} port The port number, as specified in the [protocolOptions]{@link ClientOptions} at Client instance creation (9042 by default).
* @param {Function} callback Callback to invoke with endpoint as first parameter.
* The endpoint is an string composed of the IP address and the port number in the format <code>ipAddress:port</code>.
*/
AddressTranslator.prototype.translate = function (address, port, callback) {
callback(address + ':' + port);
};
/**
* @class
* @classdesc
* {@link AddressTranslator} implementation for multi-region EC2 deployments <strong>where clients are also deployed in EC2</strong>.
* <p>
* Its distinctive feature is that it translates addresses according to the location of the Cassandra host:
* </p>
* <ul>
* <li>addresses in different EC2 regions (than the client) are unchanged</li>
* <li>addresses in the same EC2 region are <strong>translated to private IPs</strong></li>
* </ul>
* <p>
* This optimizes network costs, because Amazon charges more for communication over public IPs.
* </p>
* @constructor
*/
function EC2MultiRegionTranslator() {
}
util.inherits(EC2MultiRegionTranslator, AddressTranslator);
/**
* Addresses in the same EC2 region are translated to private IPs and addresses in
* different EC2 regions (than the client) are unchanged
*/
EC2MultiRegionTranslator.prototype.translate = function (address, port, callback) {
let newAddress = address;
const self = this;
let name;
utils.series([
function resolve(next) {
dns.reverse(address, function (err, hostNames) {
if (err) {
return next(err);
}
if (!hostNames) {
return next();
}
name = hostNames[0];
next();
});
},
function lookup(next) {
if (!name) {
return next();
}
dns.lookup(name, function (err, lookupAddress) {
if (err) {
return next(err);
}
newAddress = lookupAddress;
next();
});
}], function (err) {
if (err) {
//there was an issue while doing dns resolution
self.logError(address, err);
}
callback(newAddress + ':' + port);
});
};
/**
* Log method called to log errors that occurred while performing dns resolution.
* You can assign your own method to the class instance to do proper logging.
* @param {String} address
* @param {Error} err
*/
EC2MultiRegionTranslator.prototype.logError = function (address, err) {
//Do nothing by default
};
exports.AddressTranslator = AddressTranslator;
exports.EC2MultiRegionTranslator = EC2MultiRegionTranslator;

210
node_modules/cassandra-driver/lib/policies/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,210 @@
/*
* 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.
*/
import { Client, EmptyCallback, ExecutionOptions, Host, HostMap } from '../../';
import { types } from '../types';
export namespace policies {
function defaultAddressTranslator(): addressResolution.AddressTranslator;
function defaultLoadBalancingPolicy(localDc?: string): loadBalancing.LoadBalancingPolicy;
function defaultReconnectionPolicy(): reconnection.ReconnectionPolicy;
function defaultRetryPolicy(): retry.RetryPolicy;
function defaultSpeculativeExecutionPolicy(): speculativeExecution.SpeculativeExecutionPolicy;
function defaultTimestampGenerator(): timestampGeneration.TimestampGenerator;
namespace addressResolution {
interface AddressTranslator {
translate(address: string, port: number, callback: Function): void;
}
class EC2MultiRegionTranslator implements AddressTranslator {
translate(address: string, port: number, callback: Function): void;
}
}
namespace loadBalancing {
abstract class LoadBalancingPolicy {
init(client: Client, hosts: HostMap, callback: EmptyCallback): void;
getDistance(host: Host): types.distance;
newQueryPlan(
keyspace: string,
executionOptions: ExecutionOptions,
callback: (error: Error, iterator: Iterator<Host>) => void): void;
getOptions(): Map<string, object>;
}
class DCAwareRoundRobinPolicy extends LoadBalancingPolicy {
constructor(localDc: string);
}
class TokenAwarePolicy extends LoadBalancingPolicy {
constructor(childPolicy: LoadBalancingPolicy);
}
class AllowListPolicy extends LoadBalancingPolicy {
constructor(childPolicy: LoadBalancingPolicy, allowList: string[]);
}
class WhiteListPolicy extends AllowListPolicy {
}
class RoundRobinPolicy extends LoadBalancingPolicy {
constructor();
}
class DefaultLoadBalancingPolicy extends LoadBalancingPolicy {
constructor(options?: { localDc?: string, filter?: (host: Host) => boolean });
}
}
namespace reconnection {
class ConstantReconnectionPolicy implements ReconnectionPolicy {
constructor(delay: number);
getOptions(): Map<string, object>;
newSchedule(): Iterator<number>;
}
class ExponentialReconnectionPolicy implements ReconnectionPolicy {
constructor(baseDelay: number, maxDelay: number, startWithNoDelay?: boolean);
getOptions(): Map<string, object>;
newSchedule(): Iterator<number>;
}
interface ReconnectionPolicy {
getOptions(): Map<string, object>;
newSchedule(): Iterator<number>;
}
}
namespace retry {
class DecisionInfo {
decision: number;
consistency: types.consistencies;
}
class OperationInfo {
query: string;
executionOptions: ExecutionOptions;
nbRetry: number;
}
class IdempotenceAwareRetryPolicy extends RetryPolicy {
constructor(childPolicy: RetryPolicy);
}
class FallthroughRetryPolicy extends RetryPolicy {
constructor();
}
class RetryPolicy {
onReadTimeout(
info: OperationInfo,
consistency: types.consistencies,
received: number,
blockFor: number,
isDataPresent: boolean): DecisionInfo;
onRequestError(info: OperationInfo, consistency: types.consistencies, err: Error): DecisionInfo;
onUnavailable(
info: OperationInfo, consistency: types.consistencies, required: number, alive: boolean): DecisionInfo;
onWriteTimeout(
info: OperationInfo,
consistency: types.consistencies,
received: number,
blockFor: number,
writeType: string): DecisionInfo;
rethrowResult(): DecisionInfo;
retryResult(consistency: types.consistencies, useCurrentHost?: boolean): DecisionInfo;
}
namespace RetryDecision {
enum retryDecision {
ignore,
rethrow,
retry
}
}
}
namespace speculativeExecution {
class ConstantSpeculativeExecutionPolicy implements SpeculativeExecutionPolicy {
constructor(delay: number, maxSpeculativeExecutions: number);
getOptions(): Map<string, object>;
init(client: Client): void;
newPlan(keyspace: string, queryInfo: string | Array<object>): { nextExecution: Function };
shutdown(): void;
}
class NoSpeculativeExecutionPolicy implements SpeculativeExecutionPolicy {
constructor();
getOptions(): Map<string, object>;
init(client: Client): void;
newPlan(keyspace: string, queryInfo: string | Array<object>): { nextExecution: Function };
shutdown(): void;
}
interface SpeculativeExecutionPolicy {
getOptions(): Map<string, object>;
init(client: Client): void;
newPlan(keyspace: string, queryInfo: string|Array<object>): { nextExecution: Function };
shutdown(): void;
}
}
namespace timestampGeneration {
class MonotonicTimestampGenerator implements TimestampGenerator {
constructor(warningThreshold: number, minLogInterval: number);
getDate(): number;
next(client: Client): types.Long | number;
}
interface TimestampGenerator {
next(client: Client): types.Long|number;
}
}
}

84
node_modules/cassandra-driver/lib/policies/index.js generated vendored Normal file
View File

@@ -0,0 +1,84 @@
/*
* 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';
/**
* Contains driver tuning policies to determine [load balancing]{@link module:policies/loadBalancing},
* [retrying]{@link module:policies/retry} queries, [reconnecting]{@link module:policies/reconnection} to a node,
* [address resolution]{@link module:policies/addressResolution},
* [timestamp generation]{@link module:policies/timestampGeneration} and
* [speculative execution]{@link module:policies/speculativeExecution}.
* @module policies
*/
const addressResolution = exports.addressResolution = require('./address-resolution');
const loadBalancing = exports.loadBalancing = require('./load-balancing');
const reconnection = exports.reconnection = require('./reconnection');
const retry = exports.retry = require('./retry');
const speculativeExecution = exports.speculativeExecution = require('./speculative-execution');
const timestampGeneration = exports.timestampGeneration = require('./timestamp-generation');
/**
* Returns a new instance of the default address translator policy used by the driver.
* @returns {AddressTranslator}
*/
exports.defaultAddressTranslator = function () {
return new addressResolution.AddressTranslator();
};
/**
* Returns a new instance of the default load-balancing policy used by the driver.
* @param {string} [localDc] When provided, it sets the data center that is going to be used as local for the
* load-balancing policy instance.
* <p>When localDc is undefined, the load-balancing policy instance will use the <code>localDataCenter</code>
* provided in the {@link ClientOptions}.</p>
* @returns {LoadBalancingPolicy}
*/
exports.defaultLoadBalancingPolicy = function (localDc) {
return new loadBalancing.DefaultLoadBalancingPolicy(localDc);
};
/**
* Returns a new instance of the default retry policy used by the driver.
* @returns {RetryPolicy}
*/
exports.defaultRetryPolicy = function () {
return new retry.RetryPolicy();
};
/**
* Returns a new instance of the default reconnection policy used by the driver.
* @returns {ReconnectionPolicy}
*/
exports.defaultReconnectionPolicy = function () {
return new reconnection.ExponentialReconnectionPolicy(1000, 10 * 60 * 1000, false);
};
/**
* Returns a new instance of the default speculative execution policy used by the driver.
* @returns {SpeculativeExecutionPolicy}
*/
exports.defaultSpeculativeExecutionPolicy = function () {
return new speculativeExecution.NoSpeculativeExecutionPolicy();
};
/**
* Returns a new instance of the default timestamp generator used by the driver.
* @returns {TimestampGenerator}
*/
exports.defaultTimestampGenerator = function () {
return new timestampGeneration.MonotonicTimestampGenerator();
};

View File

@@ -0,0 +1,883 @@
/*
* 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 util = require('util');
const types = require('../types');
const utils = require('../utils.js');
const errors = require('../errors.js');
const doneIteratorObject = Object.freeze({ done: true });
const newlyUpInterval = 60000;
/** @module policies/loadBalancing */
/**
* Base class for Load Balancing Policies
* @constructor
*/
function LoadBalancingPolicy() {
}
/**
* Initializes the load balancing policy, called after the driver obtained the information of the cluster.
* @param {Client} client
* @param {HostMap} hosts
* @param {Function} callback
*/
LoadBalancingPolicy.prototype.init = function (client, hosts, callback) {
this.client = client;
this.hosts = hosts;
callback();
};
/**
* Returns the distance assigned by this policy to the provided host.
* @param {Host} host
*/
LoadBalancingPolicy.prototype.getDistance = function (host) {
return types.distance.local;
};
/**
* Returns an iterator with the hosts for a new query.
* Each new query will call this method. The first host in the result will
* then be used to perform the query.
* @param {String} keyspace Name of currently logged keyspace at <code>Client</code> level.
* @param {ExecutionOptions|null} executionOptions The information related to the execution of the request.
* @param {Function} callback The function to be invoked with the error as first parameter and the host iterator as
* second parameter.
*/
LoadBalancingPolicy.prototype.newQueryPlan = function (keyspace, executionOptions, callback) {
callback(new Error('You must implement a query plan for the LoadBalancingPolicy class'));
};
/**
* Gets an associative array containing the policy options.
*/
LoadBalancingPolicy.prototype.getOptions = function () {
return new Map();
};
/**
* This policy yield nodes in a round-robin fashion.
* @extends LoadBalancingPolicy
* @constructor
*/
function RoundRobinPolicy() {
this.index = 0;
}
util.inherits(RoundRobinPolicy, LoadBalancingPolicy);
/**
* Returns an iterator with the hosts to be used as coordinator for a query.
* @param {String} keyspace Name of currently logged keyspace at <code>Client</code> level.
* @param {ExecutionOptions|null} executionOptions The information related to the execution of the request.
* @param {Function} callback The function to be invoked with the error as first parameter and the host iterator as
* second parameter.
*/
RoundRobinPolicy.prototype.newQueryPlan = function (keyspace, executionOptions, callback) {
if (!this.hosts) {
return callback(new Error('Load balancing policy not initialized'));
}
const hosts = this.hosts.values();
const self = this;
let counter = 0;
let planIndex = self.index % hosts.length;
self.index += 1;
if (self.index >= utils.maxInt) {
self.index = 0;
}
callback(null, {
next: function () {
if (++counter > hosts.length) {
return doneIteratorObject;
}
return {value: hosts[planIndex++ % hosts.length], done: false};
}
});
};
/**
* A data-center aware Round-robin load balancing policy.
* This policy provides round-robin queries over the nodes of the local
* data center.
* @param {?String} [localDc] local datacenter name. This value overrides the 'localDataCenter' Client option \
* and is useful for cases where you have multiple execution profiles that you intend on using for routing
* requests to different data centers.
* @extends {LoadBalancingPolicy}
* @constructor
*/
function DCAwareRoundRobinPolicy(localDc) {
this.localDc = localDc;
this.index = 0;
/** @type {Array} */
this.localHostsArray = null;
}
util.inherits(DCAwareRoundRobinPolicy, LoadBalancingPolicy);
/**
* Initializes the load balancing policy.
* @param {Client} client
* @param {HostMap} hosts
* @param {Function} callback
*/
DCAwareRoundRobinPolicy.prototype.init = function (client, hosts, callback) {
this.client = client;
this.hosts = hosts;
hosts.on('add', this._cleanHostCache.bind(this));
hosts.on('remove', this._cleanHostCache.bind(this));
try {
setLocalDc(this, client, this.hosts);
} catch (err) {
return callback(err);
}
callback();
};
/**
* Returns the distance depending on the datacenter.
* @param {Host} host
*/
DCAwareRoundRobinPolicy.prototype.getDistance = function (host) {
if (host.datacenter === this.localDc) {
return types.distance.local;
}
return types.distance.ignored;
};
DCAwareRoundRobinPolicy.prototype._cleanHostCache = function () {
this.localHostsArray = null;
};
DCAwareRoundRobinPolicy.prototype._resolveLocalHosts = function() {
const hosts = this.hosts.values();
if (this.localHostsArray) {
//there were already calculated
return;
}
this.localHostsArray = [];
hosts.forEach(function (h) {
if (!h.datacenter) {
//not a remote dc node
return;
}
if (h.datacenter === this.localDc) {
this.localHostsArray.push(h);
}
}, this);
};
/**
* It returns an iterator that yields local nodes.
* @param {String} keyspace Name of currently logged keyspace at <code>Client</code> level.
* @param {ExecutionOptions|null} executionOptions The information related to the execution of the request.
* @param {Function} callback The function to be invoked with the error as first parameter and the host iterator as
* second parameter.
*/
DCAwareRoundRobinPolicy.prototype.newQueryPlan = function (keyspace, executionOptions, callback) {
if (!this.hosts) {
return callback(new Error('Load balancing policy not initialized'));
}
this.index += 1;
if (this.index >= utils.maxInt) {
this.index = 0;
}
this._resolveLocalHosts();
// Use a local reference of hosts
const localHostsArray = this.localHostsArray;
let planLocalIndex = this.index;
let counter = 0;
callback(null, {
next: function () {
let host;
if (counter++ < localHostsArray.length) {
host = localHostsArray[planLocalIndex++ % localHostsArray.length];
return { value: host, done: false };
}
return doneIteratorObject;
}
});
};
/**
* Gets an associative array containing the policy options.
*/
DCAwareRoundRobinPolicy.prototype.getOptions = function () {
return new Map([
['localDataCenter', this.localDc ]
]);
};
/**
* A wrapper load balancing policy that add token awareness to a child policy.
* @param {LoadBalancingPolicy} childPolicy
* @extends LoadBalancingPolicy
* @constructor
*/
function TokenAwarePolicy (childPolicy) {
if (!childPolicy) {
throw new Error("You must specify a child load balancing policy");
}
this.childPolicy = childPolicy;
}
util.inherits(TokenAwarePolicy, LoadBalancingPolicy);
TokenAwarePolicy.prototype.init = function (client, hosts, callback) {
this.client = client;
this.hosts = hosts;
this.childPolicy.init(client, hosts, callback);
};
TokenAwarePolicy.prototype.getDistance = function (host) {
return this.childPolicy.getDistance(host);
};
/**
* Returns the hosts to use for a new query.
* The returned plan will return local replicas first, if replicas can be determined, followed by the plan of the
* child policy.
* @param {String} keyspace Name of currently logged keyspace at <code>Client</code> level.
* @param {ExecutionOptions|null} executionOptions The information related to the execution of the request.
* @param {Function} callback The function to be invoked with the error as first parameter and the host iterator as
* second parameter.
*/
TokenAwarePolicy.prototype.newQueryPlan = function (keyspace, executionOptions, callback) {
let routingKey;
if (executionOptions) {
routingKey = executionOptions.getRoutingKey();
if (executionOptions.getKeyspace()) {
keyspace = executionOptions.getKeyspace();
}
}
let replicas;
if (routingKey) {
replicas = this.client.getReplicas(keyspace, routingKey);
}
if (!routingKey || !replicas) {
return this.childPolicy.newQueryPlan(keyspace, executionOptions, callback);
}
const iterator = new TokenAwareIterator(keyspace, executionOptions, replicas, this.childPolicy);
iterator.iterate(callback);
};
/**
* An iterator that holds the context for the subsequent next() calls
* @param {String} keyspace
* @param {ExecutionOptions} execOptions
* @param {Array} replicas
* @param childPolicy
* @constructor
* @ignore
*/
function TokenAwareIterator(keyspace, execOptions, replicas, childPolicy) {
this.keyspace = keyspace;
this.childPolicy = childPolicy;
this.options = execOptions;
this.localReplicas = [];
this.replicaIndex = 0;
this.replicaMap = {};
this.childIterator = null;
// Memoize the local replicas
// The amount of local replicas should be defined before start iterating, in order to select an
// appropriate (pseudo random) startIndex
for (let i = 0; i < replicas.length; i++) {
const host = replicas[i];
if (this.childPolicy.getDistance(host) !== types.distance.local) {
continue;
}
this.replicaMap[host.address] = true;
this.localReplicas.push(host);
}
// We use a PRNG to set the replica index
// We only care about proportional fair scheduling between replicas of a given token
// Math.random() has an extremely short permutation cycle length but we don't care about collisions
this.startIndex = Math.floor(Math.random() * this.localReplicas.length);
}
TokenAwareIterator.prototype.iterate = function (callback) {
//Load the child policy hosts
const self = this;
this.childPolicy.newQueryPlan(this.keyspace, this.options, function (err, iterator) {
if (err) {
return callback(err);
}
//get the iterator of the child policy in case is needed
self.childIterator = iterator;
callback(null, {
next: function () { return self.computeNext(); }
});
});
};
TokenAwareIterator.prototype.computeNext = function () {
let host;
if (this.replicaIndex < this.localReplicas.length) {
host = this.localReplicas[(this.startIndex + (this.replicaIndex++)) % this.localReplicas.length];
return { value: host, done: false };
}
// Return hosts from child policy
let item;
while ((item = this.childIterator.next()) && !item.done) {
if (this.replicaMap[item.value.address]) {
// Avoid yielding local replicas from the child load balancing policy query plan
continue;
}
return item;
}
return doneIteratorObject;
};
/**
* Gets an associative array containing the policy options.
*/
TokenAwarePolicy.prototype.getOptions = function () {
const map = new Map([
['childPolicy', this.childPolicy.constructor !== undefined ? this.childPolicy.constructor.name : null ]
]);
if (this.childPolicy instanceof DCAwareRoundRobinPolicy) {
map.set('localDataCenter', this.childPolicy.localDc);
}
return map;
};
/**
* Create a new policy that wraps the provided child policy but only "allow" hosts
* from the provided list.
* @class
* @classdesc
* A load balancing policy wrapper that ensure that only hosts from a provided
* allow list will ever be returned.
* <p>
* This policy wraps another load balancing policy and will delegate the choice
* of hosts to the wrapped policy with the exception that only hosts contained
* in the allow list provided when constructing this policy will ever be
* returned. Any host not in the while list will be considered ignored
* and thus will not be connected to.
* <p>
* This policy can be useful to ensure that the driver only connects to a
* predefined set of hosts. Keep in mind however that this policy defeats
* somewhat the host auto-detection of the driver. As such, this policy is only
* useful in a few special cases or for testing, but is not optimal in general.
* If all you want to do is limiting connections to hosts of the local
* data-center then you should use DCAwareRoundRobinPolicy and *not* this policy
* in particular.
* @param {LoadBalancingPolicy} childPolicy the wrapped policy.
* @param {Array.<string>} allowList The hosts address in the format ipAddress:port.
* Only hosts from this list may get connected
* to (whether they will get connected to or not depends on the child policy).
* @extends LoadBalancingPolicy
* @constructor
*/
function AllowListPolicy (childPolicy, allowList) {
if (!childPolicy) {
throw new Error("You must specify a child load balancing policy");
}
if (!Array.isArray(allowList)) {
throw new Error("You must provide the list of allowed host addresses");
}
this.childPolicy = childPolicy;
this.allowList = new Map(allowList.map(address => [ address, true ]));
}
util.inherits(AllowListPolicy, LoadBalancingPolicy);
AllowListPolicy.prototype.init = function (client, hosts, callback) {
this.childPolicy.init(client, hosts, callback);
};
/**
* Uses the child policy to return the distance to the host if included in the allow list.
* Any host not in the while list will be considered ignored.
* @param host
*/
AllowListPolicy.prototype.getDistance = function (host) {
if (!this._contains(host)) {
return types.distance.ignored;
}
return this.childPolicy.getDistance(host);
};
/**
* @param {Host} host
* @returns {boolean}
* @private
*/
AllowListPolicy.prototype._contains = function (host) {
return !!this.allowList.get(host.address);
};
/**
* Returns the hosts to use for a new query filtered by the allow list.
*/
AllowListPolicy.prototype.newQueryPlan = function (keyspace, info, callback) {
const self = this;
this.childPolicy.newQueryPlan(keyspace, info, function (err, iterator) {
if (err) {
return callback(err);
}
callback(null, self._filter(iterator));
});
};
AllowListPolicy.prototype._filter = function (childIterator) {
const self = this;
return {
next: function () {
const item = childIterator.next();
if (!item.done && !self._contains(item.value)) {
return this.next();
}
return item;
}
};
};
/**
* Gets an associative array containing the policy options.
*/
AllowListPolicy.prototype.getOptions = function () {
return new Map([
['childPolicy', this.childPolicy.constructor !== undefined ? this.childPolicy.constructor.name : null ],
['allowList', Array.from(this.allowList.keys())]
]);
};
/**
* Creates a new instance of the policy.
* @classdesc
* Exposed for backward-compatibility only, it's recommended that you use {@link AllowListPolicy} instead.
* @param {LoadBalancingPolicy} childPolicy the wrapped policy.
* @param {Array.<string>} allowList The hosts address in the format ipAddress:port.
* Only hosts from this list may get connected to (whether they will get connected to or not depends on the child
* policy).
* @extends AllowListPolicy
* @deprecated Use allow-list instead. It will be removed in future major versions.
* @constructor
*/
function WhiteListPolicy(childPolicy, allowList) {
AllowListPolicy.call(this, childPolicy, allowList);
}
util.inherits(WhiteListPolicy, AllowListPolicy);
/**
* A load-balancing policy implementation that attempts to fairly distribute the load based on the amount of in-flight
* request per hosts. The local replicas are initially shuffled and
* <a href="https://www.eecs.harvard.edu/~michaelm/postscripts/mythesis.pdf">between the first two nodes in the
* shuffled list, the one with fewer in-flight requests is selected as coordinator</a>.
*
* <p>
* Additionally, it detects unresponsive replicas and reorders them at the back of the query plan.
* </p>
*
* <p>
* For graph analytics queries, it uses the preferred analytics graph server previously obtained by driver as first
* host in the query plan.
* </p>
*/
class DefaultLoadBalancingPolicy extends LoadBalancingPolicy {
/**
* Creates a new instance of <code>DefaultLoadBalancingPolicy</code>.
* @param {String|Object} [options] The local data center name or the optional policy options object.
* <p>
* Note that when providing the local data center name, it overrides <code>localDataCenter</code> option at
* <code>Client</code> level.
* </p>
* @param {String} [options.localDc] local data center name. This value overrides the 'localDataCenter' Client option
* and is useful for cases where you have multiple execution profiles that you intend on using for routing
* requests to different data centers.
* @param {Function} [options.filter] A function to apply to determine if hosts are included in the query plan.
* The function takes a Host parameter and returns a Boolean.
*/
constructor(options) {
super();
if (typeof options === 'string') {
options = { localDc: options };
} else if (!options) {
options = utils.emptyObject;
}
this._client = null;
this._hosts = null;
this._filteredHosts = null;
this._preferredHost = null;
this._index = 0;
this.localDc = options.localDc;
this._filter = options.filter || this._defaultFilter;
// Allow some checks to be injected
if (options.isHostNewlyUp) {
this._isHostNewlyUp = options.isHostNewlyUp;
}
if (options.healthCheck) {
this._healthCheck = options.healthCheck;
}
if (options.compare) {
this._compare = options.compare;
}
if (options.getReplicas) {
this._getReplicas = options.getReplicas;
}
}
/**
* Initializes the load balancing policy, called after the driver obtained the information of the cluster.
* @param {Client} client
* @param {HostMap} hosts
* @param {Function} callback
*/
init(client, hosts, callback) {
this._client = client;
this._hosts = hosts;
// Clean local host cache
this._hosts.on('add', () => this._filteredHosts = null);
this._hosts.on('remove', () => this._filteredHosts = null);
try {
setLocalDc(this, client, this._hosts);
} catch (err) {
return callback(err);
}
callback();
}
/**
* Returns the distance assigned by this policy to the provided host, relatively to the client instance.
* @param {Host} host
*/
getDistance(host) {
if (this._preferredHost !== null && host === this._preferredHost) {
// Set the last preferred host as local.
// It ensures that the pool for the graph analytics host has the appropriate size
return types.distance.local;
}
if (!this._filter(host)) {
return types.distance.ignored;
}
return host.datacenter === this.localDc ? types.distance.local : types.distance.ignored;
}
/**
* Returns a host iterator to be used for a query execution.
* @override
* @param {String} keyspace
* @param {ExecutionOptions} executionOptions
* @param {Function} callback
*/
newQueryPlan(keyspace, executionOptions, callback) {
let routingKey;
let preferredHost;
if (executionOptions) {
routingKey = executionOptions.getRoutingKey();
if (executionOptions.getKeyspace()) {
keyspace = executionOptions.getKeyspace();
}
preferredHost = executionOptions.getPreferredHost();
}
let iterable;
if (!keyspace || !routingKey) {
iterable = this._getLocalHosts();
} else {
iterable = this._getReplicasAndLocalHosts(keyspace, routingKey);
}
if (preferredHost) {
// Set it on an instance level field to set the distance
this._preferredHost = preferredHost;
iterable = DefaultLoadBalancingPolicy._getPreferredHostFirst(preferredHost, iterable);
}
return callback(null, iterable);
}
/**
* Yields the preferred host first, followed by the host in the provided iterable
* @param preferredHost
* @param iterable
* @private
*/
static *_getPreferredHostFirst(preferredHost, iterable) {
yield preferredHost;
for (const host of iterable) {
if (host !== preferredHost) {
yield host;
}
}
}
/**
* Yields the local hosts without the replicas already yielded
* @param {Array<Host>} [localReplicas] The local replicas that we should avoid to include again
* @private
*/
*_getLocalHosts(localReplicas) {
// Use a local reference
const hosts = this._getFilteredLocalHosts();
const initialIndex = this._getIndex();
// indexOf() over an Array is a O(n) operation but given that there should be 3 to 7 replicas,
// it shouldn't be an expensive call. Additionally, this will only be executed when the local replicas
// have been exhausted in a lazy manner.
const canBeYield = localReplicas
? h => localReplicas.indexOf(h) === -1
: h => true;
for (let i = 0; i < hosts.length; i++) {
const h = hosts[(i + initialIndex) % hosts.length];
if (canBeYield(h) && h.isUp()) {
yield h;
}
}
}
_getReplicasAndLocalHosts(keyspace, routingKey) {
let replicas = this._getReplicas(keyspace, routingKey);
if (replicas === null) {
return this._getLocalHosts();
}
const filteredReplicas = [];
let newlyUpReplica = null;
let newlyUpReplicaTimestamp = Number.MIN_SAFE_INTEGER;
let unhealthyReplicas = 0;
// Filter by DC, predicate and UP replicas
// Use the same iteration to perform other checks: whether if its newly UP or unhealthy
// As this is part of the hot path, we use a simple loop and avoid using Array.prototype.filter() + closure
for (let i = 0; i < replicas.length; i++) {
const h = replicas[i];
if (!this._filter(h) || h.datacenter !== this.localDc || !h.isUp()) {
continue;
}
const isUpSince = this._isHostNewlyUp(h);
if (isUpSince !== null && isUpSince > newlyUpReplicaTimestamp) {
newlyUpReplica = h;
newlyUpReplicaTimestamp = isUpSince;
}
if (newlyUpReplica === null && !this._healthCheck(h)) {
unhealthyReplicas++;
}
filteredReplicas.push(h);
}
replicas = filteredReplicas;
// Shuffle remaining local replicas
utils.shuffleArray(replicas);
if (replicas.length < 3) {
// Avoid reordering replicas of a set of 2 as we could be doing more harm than good
return this.yieldReplicasFirst(replicas);
}
let temp;
if (newlyUpReplica === null) {
if (unhealthyReplicas > 0 && unhealthyReplicas < Math.floor(replicas.length / 2 + 1)) {
// There is one or more unhealthy replicas and there is a majority of healthy replicas
this._sendUnhealthyToTheBack(replicas, unhealthyReplicas);
}
}
else if ((newlyUpReplica === replicas[0] || newlyUpReplica === replicas[1]) && Math.random() * 4 >= 1) {
// There is a newly UP replica and the replica in first or second position is the most recent replica
// marked as UP and dice roll 1d4!=1 -> Send it to the back of the Array
const index = newlyUpReplica === replicas[0] ? 0 : 1;
temp = replicas[replicas.length - 1];
replicas[replicas.length - 1] = replicas[index];
replicas[index] = temp;
}
if (this._compare(replicas[1], replicas[0]) > 0) {
// Power of two random choices
temp = replicas[0];
replicas[0] = replicas[1];
replicas[1] = temp;
}
return this.yieldReplicasFirst(replicas);
}
/**
* Yields the local replicas followed by the rest of local nodes.
* @param {Array<Host>} replicas The local replicas
*/
*yieldReplicasFirst(replicas) {
for (let i = 0; i < replicas.length; i++) {
yield replicas[i];
}
yield* this._getLocalHosts(replicas);
}
_isHostNewlyUp(h) {
return (h.isUpSince !== null && Date.now() - h.isUpSince < newlyUpInterval) ? h.isUpSince : null;
}
/**
* Returns a boolean determining whether the host health is ok or not.
* A Host is considered unhealthy when there are enough items in the queue (10 items in-flight) but the
* Host is not responding to those requests.
* @param {Host} h
* @return {boolean}
* @private
*/
_healthCheck(h) {
return !(h.getInFlight() >= 10 && h.getResponseCount() <= 1);
}
/**
* Compares to host and returns 1 if it needs to favor the first host otherwise, -1.
* @return {number}
* @private
*/
_compare(h1, h2) {
return h1.getInFlight() < h2.getInFlight() ? 1 : -1;
}
_getReplicas(keyspace, routingKey) {
return this._client.getReplicas(keyspace, routingKey);
}
/**
* Returns an Array of hosts filtered by DC and predicate.
* @returns {Array<Host>}
* @private
*/
_getFilteredLocalHosts() {
if (this._filteredHosts === null) {
this._filteredHosts = this._hosts.values()
.filter(h => this._filter(h) && h.datacenter === this.localDc);
}
return this._filteredHosts;
}
_getIndex() {
const result = this._index++;
// Overflow protection
if (this._index === 0x7fffffff) {
this._index = 0;
}
return result;
}
_sendUnhealthyToTheBack(replicas, unhealthyReplicas) {
let counter = 0;
// Start from the back, move backwards and stop once all unhealthy replicas are at the back
for (let i = replicas.length - 1; i >= 0 && counter < unhealthyReplicas; i--) {
const host = replicas[i];
if (this._healthCheck(host)) {
continue;
}
const targetIndex = replicas.length - 1 - counter;
if (targetIndex !== i) {
const temp = replicas[targetIndex];
replicas[targetIndex] = host;
replicas[i] = temp;
}
counter++;
}
}
_defaultFilter() {
return true;
}
/**
* Gets an associative array containing the policy options.
*/
getOptions() {
return new Map([
['localDataCenter', this.localDc ],
['filterFunction', this._filter !== this._defaultFilter ]
]);
}
}
/**
* Validates and sets the local data center to be used.
* @param {LoadBalancingPolicy} lbp
* @param {Client} client
* @param {HostMap} hosts
* @private
*/
function setLocalDc(lbp, client, hosts) {
if (!(lbp instanceof LoadBalancingPolicy)) {
throw new errors.DriverInternalError('LoadBalancingPolicy instance was not provided');
}
if (client && client.options) {
if (lbp.localDc && !client.options.localDataCenter) {
client.log('info', `Local data center '${lbp.localDc}' was provided as an argument to the load-balancing` +
` policy. It is preferable to specify the local data center using 'localDataCenter' in Client` +
` options instead when your application is targeting a single data center.`);
}
// If localDc is unset, use value set in client options.
lbp.localDc = lbp.localDc || client.options.localDataCenter;
}
const dcs = getDataCenters(hosts);
if (!lbp.localDc) {
throw new errors.ArgumentError(
`'localDataCenter' is not defined in Client options and also was not specified in constructor.` +
` At least one is required. Available DCs are: [${Array.from(dcs)}]`);
}
if (!dcs.has(lbp.localDc)) {
throw new errors.ArgumentError(`Datacenter ${lbp.localDc} was not found. Available DCs are: [${Array.from(dcs)}]`);
}
}
function getDataCenters(hosts) {
return new Set(hosts.values().map(h => h.datacenter));
}
module.exports = {
AllowListPolicy,
DCAwareRoundRobinPolicy,
DefaultLoadBalancingPolicy,
LoadBalancingPolicy,
RoundRobinPolicy,
TokenAwarePolicy,
// Deprecated: for backward compatibility only.
WhiteListPolicy
};

View File

@@ -0,0 +1,157 @@
/*
* 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 util = require('util');
/** @module policies/reconnection */
/**
* Base class for Reconnection Policies
* @constructor
*/
function ReconnectionPolicy() {
}
/**
* A new reconnection schedule.
* @returns {{next: function}} An infinite iterator
*/
ReconnectionPolicy.prototype.newSchedule = function () {
throw new Error('You must implement a new schedule for the Reconnection class');
};
/**
* Gets an associative array containing the policy options.
*/
ReconnectionPolicy.prototype.getOptions = function () {
return new Map();
};
/**
* A reconnection policy that waits a constant time between each reconnection attempt.
* @param {Number} delay Delay in ms
* @constructor
*/
function ConstantReconnectionPolicy(delay) {
this.delay = delay;
}
util.inherits(ConstantReconnectionPolicy, ReconnectionPolicy);
/**
* A new reconnection schedule that returns the same next delay value
* @returns {{next: Function}} An infinite iterator
*/
ConstantReconnectionPolicy.prototype.newSchedule = function () {
const self = this;
return {
next: function () {
return {value: self.delay, done: false};
}
};
};
/**
* Gets an associative array containing the policy options.
*/
ConstantReconnectionPolicy.prototype.getOptions = function () {
return new Map([['delay', this.delay ]]);
};
/**
* A reconnection policy that waits exponentially longer between each
* reconnection attempt (but keeps a constant delay once a maximum delay is reached).
* <p>
* A random amount of jitter (+/- 15%) will be added to the pure exponential delay value to avoid situations
* where many clients are in the reconnection process at exactly the same time. The jitter will never cause the
* delay to be less than the base delay, or more than the max delay.
* </p>
* @param {Number} baseDelay The base delay in milliseconds to use for the schedules created by this policy.
* @param {Number} maxDelay The maximum delay in milliseconds to wait between two reconnection attempt.
* @param {Boolean} startWithNoDelay Determines if the first attempt should be zero delay
* @constructor
*/
function ExponentialReconnectionPolicy(baseDelay, maxDelay, startWithNoDelay) {
this.baseDelay = baseDelay;
this.maxDelay = maxDelay;
this.startWithNoDelay = startWithNoDelay;
}
util.inherits(ExponentialReconnectionPolicy, ReconnectionPolicy);
/**
* A new schedule that uses an exponentially growing delay between reconnection attempts.
* @returns {{next: Function}} An infinite iterator.
*/
ExponentialReconnectionPolicy.prototype.newSchedule = function* () {
let index = this.startWithNoDelay ? -1 : 0;
while (true) {
let delay = 0;
if (index >= 64) {
delay = this.maxDelay;
} else if (index !== -1) {
delay = Math.min(Math.pow(2, index) * this.baseDelay, this.maxDelay);
}
index++;
yield this._addJitter(delay);
}
};
/**
* Adds a random portion of +-15% to the delay provided.
* Initially, its adds a random value of 15% to avoid reconnection before reaching the base delay.
* When the schedule reaches max delay, only subtracts a random portion of 15%.
*/
ExponentialReconnectionPolicy.prototype._addJitter = function (value) {
if (value === 0) {
// Instant reconnection without jitter
return value;
}
// Use the formula: 85% + rnd() * 30% to calculate the percentage of the original delay
let minPercentage = 0.85;
let range = 0.30;
if (!this.startWithNoDelay && value === this.baseDelay) {
// Between 100% to 115% of the original value
minPercentage = 1;
range = 0.15;
} else if (value === this.maxDelay) {
// Between 85% to 100% of the original value
range = 0.15;
}
return Math.floor(value * (Math.random() * range + minPercentage));
};
/**
* Gets an associative array containing the policy options.
*/
ExponentialReconnectionPolicy.prototype.getOptions = function () {
return new Map([
['baseDelay', this.baseDelay ],
['maxDelay', this.maxDelay ],
['startWithNoDelay', this.startWithNoDelay ]
]);
};
exports.ReconnectionPolicy = ReconnectionPolicy;
exports.ConstantReconnectionPolicy = ConstantReconnectionPolicy;
exports.ExponentialReconnectionPolicy = ExponentialReconnectionPolicy;

276
node_modules/cassandra-driver/lib/policies/retry.js generated vendored Normal file
View File

@@ -0,0 +1,276 @@
/*
* 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 util = require('util');
/** @module policies/retry */
/**
* Base and default RetryPolicy.
* Determines what to do when the drivers runs into an specific Cassandra exception
* @constructor
*/
function RetryPolicy() {
}
/**
* Determines what to do when the driver gets an UnavailableException response from a Cassandra node.
* @param {OperationInfo} info
* @param {Number} consistency The [consistency]{@link module:types~consistencies} level of the query that triggered
* the exception.
* @param {Number} required The number of replicas whose response is required to achieve the
* required [consistency]{@link module:types~consistencies}.
* @param {Number} alive The number of replicas that were known to be alive when the request had been processed
* (since an unavailable exception has been triggered, there will be alive &lt; required)
* @returns {DecisionInfo}
*/
RetryPolicy.prototype.onUnavailable = function (info, consistency, required, alive) {
if (info.nbRetry > 0) {
return this.rethrowResult();
}
return this.retryResult(undefined, false);
};
/**
* Determines what to do when the driver gets a ReadTimeoutException response from a Cassandra node.
* @param {OperationInfo} info
* @param {Number} consistency The [consistency]{@link module:types~consistencies} level of the query that triggered
* the exception.
* @param {Number} received The number of nodes having answered the request.
* @param {Number} blockFor The number of replicas whose response is required to achieve the
* required [consistency]{@link module:types~consistencies}.
* @param {Boolean} isDataPresent When <code>false</code>, it means the replica that was asked for data has not responded.
* @returns {DecisionInfo}
*/
RetryPolicy.prototype.onReadTimeout = function (info, consistency, received, blockFor, isDataPresent) {
if (info.nbRetry > 0) {
return this.rethrowResult();
}
return ((received >= blockFor && !isDataPresent) ?
this.retryResult() :
this.rethrowResult());
};
/**
* Determines what to do when the driver gets a WriteTimeoutException response from a Cassandra node.
* @param {OperationInfo} info
* @param {Number} consistency The [consistency]{@link module:types~consistencies} level of the query that triggered
* the exception.
* @param {Number} received The number of nodes having acknowledged the request.
* @param {Number} blockFor The number of replicas whose acknowledgement is required to achieve the required
* [consistency]{@link module:types~consistencies}.
* @param {String} writeType A <code>string</code> that describes the type of the write that timed out ("SIMPLE"
* / "BATCH" / "BATCH_LOG" / "UNLOGGED_BATCH" / "COUNTER").
* @returns {DecisionInfo}
*/
RetryPolicy.prototype.onWriteTimeout = function (info, consistency, received, blockFor, writeType) {
if (info.nbRetry > 0) {
return this.rethrowResult();
}
// If the batch log write failed, retry the operation as this might just be we were unlucky at picking candidates
return writeType === "BATCH_LOG" ? this.retryResult() : this.rethrowResult();
};
/**
* Defines whether to retry and at which consistency level on an unexpected error.
* <p>
* This method might be invoked in the following situations:
* </p>
* <ol>
* <li>On a client timeout, while waiting for the server response
* (see [socketOptions.readTimeout]{@link ClientOptions}), being the error an instance of
* [OperationTimedOutError]{@link module:errors~OperationTimedOutError}.</li>
* <li>On a connection error (socket closed, etc.).</li>
* <li>When the contacted host replies with an error, such as <code>overloaded</code>, <code>isBootstrapping</code>,
* </code>serverError, etc. In this case, the error is instance of [ResponseError]{@link module:errors~ResponseError}.
* </li>
* </ol>
* <p>
* Note that when this method is invoked, <em>the driver cannot guarantee that the mutation has been effectively
* applied server-side</em>; a retry should only be attempted if the request is known to be idempotent.
* </p>
* @param {OperationInfo} info
* @param {Number|undefined} consistency The [consistency]{@link module:types~consistencies} level of the query that triggered
* the exception.
* @param {Error} err The error that caused this request to fail.
* @returns {DecisionInfo}
*/
RetryPolicy.prototype.onRequestError = function (info, consistency, err) {
// The default implementation triggers a retry on the next host in the query plan with the same consistency level,
// regardless of the statement's idempotence, for historical reasons.
return this.retryResult(undefined, false);
};
/**
* Returns a {@link DecisionInfo} to retry the request with the given [consistency]{@link module:types~consistencies}.
* @param {Number|undefined} [consistency] When specified, it retries the request with the given consistency.
* @param {Boolean} [useCurrentHost] When specified, determines if the retry should be made using the same coordinator.
* Default: true.
* @returns {DecisionInfo}
*/
RetryPolicy.prototype.retryResult = function (consistency, useCurrentHost) {
return {
decision: RetryPolicy.retryDecision.retry,
consistency: consistency,
useCurrentHost: useCurrentHost !== false
};
};
/**
* Returns a {@link DecisionInfo} to callback in error when a err is obtained for a given request.
* @returns {DecisionInfo}
*/
RetryPolicy.prototype.rethrowResult = function () {
return { decision: RetryPolicy.retryDecision.rethrow };
};
/**
* Determines the retry decision for the retry policies.
* @type {Object}
* @property {Number} rethrow
* @property {Number} retry
* @property {Number} ignore
* @static
*/
RetryPolicy.retryDecision = {
rethrow: 0,
retry: 1,
ignore: 2
};
/**
* Creates a new instance of <code>IdempotenceAwareRetryPolicy</code>.
* @classdesc
* A retry policy that avoids retrying non-idempotent statements.
* <p>
* In case of write timeouts or unexpected errors, this policy will always return
* [rethrowResult()]{@link module:policies/retry~RetryPolicy#rethrowResult} if the statement is deemed non-idempotent
* (see [QueryOptions.isIdempotent]{@link QueryOptions}).
* <p/>
* For all other cases, this policy delegates the decision to the child policy.
* @param {RetryPolicy} [childPolicy] The child retry policy to wrap. When not defined, it will use an instance of
* [RetryPolicy]{@link module:policies/retry~RetryPolicy} as child policy.
* @extends module:policies/retry~RetryPolicy
* @constructor
* @deprecated Since version 4.0 non-idempotent operations are never tried for write timeout or request error, use the
* default retry policy instead.
*/
function IdempotenceAwareRetryPolicy(childPolicy) {
this._childPolicy = childPolicy || new RetryPolicy();
}
util.inherits(IdempotenceAwareRetryPolicy, RetryPolicy);
IdempotenceAwareRetryPolicy.prototype.onReadTimeout = function (info, consistency, received, blockFor, isDataPresent) {
return this._childPolicy.onReadTimeout(info, consistency, received, blockFor, isDataPresent);
};
/**
* If the query is not idempotent, it returns a rethrow decision. Otherwise, it relies on the child policy to decide.
*/
IdempotenceAwareRetryPolicy.prototype.onRequestError = function (info, consistency, err) {
if (info.executionOptions.isIdempotent()) {
return this._childPolicy.onRequestError(info, consistency, err);
}
return this.rethrowResult();
};
IdempotenceAwareRetryPolicy.prototype.onUnavailable = function (info, consistency, required, alive) {
return this._childPolicy.onUnavailable(info, consistency, required, alive);
};
/**
* If the query is not idempotent, it return a rethrow decision. Otherwise, it relies on the child policy to decide.
*/
IdempotenceAwareRetryPolicy.prototype.onWriteTimeout = function (info, consistency, received, blockFor, writeType) {
if (info.executionOptions.isIdempotent()) {
return this._childPolicy.onWriteTimeout(info, consistency, received, blockFor, writeType);
}
return this.rethrowResult();
};
/**
* Creates a new instance of FallthroughRetryPolicy.
* @classdesc
* A retry policy that never retries nor ignores.
* <p>
* All of the methods of this retry policy unconditionally return
* [rethrow]{@link module:policies/retry~Retry#rethrowResult()}. If this policy is used, retry logic will have to be
* implemented in business code.
* </p>
* @alias module:policies/retry~FallthroughRetryPolicy
* @extends RetryPolicy
* @constructor
*/
function FallthroughRetryPolicy() {
}
util.inherits(FallthroughRetryPolicy, RetryPolicy);
/**
* Implementation of RetryPolicy method that returns [rethrow]{@link module:policies/retry~Retry#rethrowResult()}.
*/
FallthroughRetryPolicy.prototype.onReadTimeout = function () {
return this.rethrowResult();
};
/**
* Implementation of RetryPolicy method that returns [rethrow]{@link module:policies/retry~Retry#rethrowResult()}.
*/
FallthroughRetryPolicy.prototype.onRequestError = function () {
return this.rethrowResult();
};
/**
* Implementation of RetryPolicy method that returns [rethrow]{@link module:policies/retry~Retry#rethrowResult()}.
*/
FallthroughRetryPolicy.prototype.onUnavailable = function () {
return this.rethrowResult();
};
/**
* Implementation of RetryPolicy method that returns [rethrow]{@link module:policies/retry~Retry#rethrowResult()}.
*/
FallthroughRetryPolicy.prototype.onWriteTimeout = function () {
return this.rethrowResult();
};
/**
* Decision information
* @typedef {Object} DecisionInfo
* @property {Number} decision The decision as specified in
* [retryDecision]{@link module:policies/retry~RetryPolicy.retryDecision}.
* @property {Number} [consistency] The [consistency level]{@link module:types~consistencies}.
* @property {useCurrentHost} [useCurrentHost] Determines if it should use the same host to retry the request.
* <p>
* In the case that the current host is not available anymore, it will be retried on the next host even when
* <code>useCurrentHost</code> is set to <code>true</code>.
* </p>
*/
/**
* Information of the execution to be used to determine whether the operation should be retried.
* @typedef {Object} OperationInfo
* @property {String} query The query that was executed.
* @param {ExecutionOptions} executionOptions The options related to the execution of the request.
* @property {Number} nbRetry The number of retries already performed for this operation.
*/
exports.IdempotenceAwareRetryPolicy = IdempotenceAwareRetryPolicy;
exports.FallthroughRetryPolicy = FallthroughRetryPolicy;
exports.RetryPolicy = RetryPolicy;

View File

@@ -0,0 +1,143 @@
/*
* 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 util = require('util');
const errors = require('../errors');
/** @module policies/speculativeExecution */
/**
* @classdesc
* The policy that decides if the driver will send speculative queries to the next hosts when the current host takes too
* long to respond.
* <p>Note that only idempotent statements will be speculatively retried.</p>
* @constructor
* @abstract
*/
function SpeculativeExecutionPolicy() {
}
/**
* Initialization method that gets invoked on Client startup.
* @param {Client} client
* @abstract
*/
SpeculativeExecutionPolicy.prototype.init = function (client) {
};
/**
* Gets invoked at client shutdown, giving the opportunity to the implementor to perform cleanup.
* @abstract
*/
SpeculativeExecutionPolicy.prototype.shutdown = function () {
};
/**
* Gets the plan to use for a new query.
* Returns an object with a <code>nextExecution()</code> method, which returns a positive number representing the
* amount of milliseconds to delay the next execution or a non-negative number to avoid further executions.
* @param {String} keyspace The currently logged keyspace.
* @param {String|Array<String>} queryInfo The query, or queries in the case of batches, for which to build a plan.
* @return {{nextExecution: function}}
* @abstract
*/
SpeculativeExecutionPolicy.prototype.newPlan = function (keyspace, queryInfo) {
throw new Error('You must implement newPlan() method in the SpeculativeExecutionPolicy');
};
/**
* Gets an associative array containing the policy options.
*/
SpeculativeExecutionPolicy.prototype.getOptions = function () {
return new Map();
};
/**
* Creates a new instance of NoSpeculativeExecutionPolicy.
* @classdesc
* A {@link SpeculativeExecutionPolicy} that never schedules speculative executions.
* @constructor
* @extends {SpeculativeExecutionPolicy}
*/
function NoSpeculativeExecutionPolicy() {
this._plan = {
nextExecution: function () {
return -1;
}
};
}
util.inherits(NoSpeculativeExecutionPolicy, SpeculativeExecutionPolicy);
NoSpeculativeExecutionPolicy.prototype.newPlan = function () {
return this._plan;
};
/**
* Creates a new instance of ConstantSpeculativeExecutionPolicy.
* @classdesc
* A {@link SpeculativeExecutionPolicy} that schedules a given number of speculative executions,
* separated by a fixed delay.
* @constructor
* @param {Number} delay The delay between each speculative execution.
* @param {Number} maxSpeculativeExecutions The amount of speculative executions that should be scheduled after the
* initial execution. Must be strictly positive.
* @extends {SpeculativeExecutionPolicy}
*/
function ConstantSpeculativeExecutionPolicy(delay, maxSpeculativeExecutions) {
if (!(delay >= 0)) {
throw new errors.ArgumentError('delay must be a positive number or zero');
}
if (!(maxSpeculativeExecutions > 0)) {
throw new errors.ArgumentError('maxSpeculativeExecutions must be a positive number');
}
this._delay = delay;
this._maxSpeculativeExecutions = maxSpeculativeExecutions;
}
util.inherits(ConstantSpeculativeExecutionPolicy, SpeculativeExecutionPolicy);
ConstantSpeculativeExecutionPolicy.prototype.newPlan = function () {
let executions = 0;
const self = this;
return {
nextExecution: function () {
if (executions++ < self._maxSpeculativeExecutions) {
return self._delay;
}
return -1;
}
};
};
/**
* Gets an associative array containing the policy options.
*/
ConstantSpeculativeExecutionPolicy.prototype.getOptions = function () {
return new Map([
['delay', this._delay ],
['maxSpeculativeExecutions', this._maxSpeculativeExecutions ]
]);
};
exports.NoSpeculativeExecutionPolicy = NoSpeculativeExecutionPolicy;
exports.SpeculativeExecutionPolicy = SpeculativeExecutionPolicy;
exports.ConstantSpeculativeExecutionPolicy = ConstantSpeculativeExecutionPolicy;

View File

@@ -0,0 +1,170 @@
/*
* 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 util = require('util');
const { Long } = require('../types');
const errors = require('../errors');
/** @module policies/timestampGeneration */
/**
* Defines the maximum date in milliseconds that can be represented in microseconds using Number ((2 ^ 53) / 1000)
* @const
* @private
*/
const _maxSafeNumberDate = 9007199254740;
/**
* A long representing the value 1000
* @const
* @private
*/
const _longOneThousand = Long.fromInt(1000);
/**
* Creates a new instance of {@link TimestampGenerator}.
* @classdesc
* Generates client-side, microsecond-precision query timestamps.
* <p>
* Given that Cassandra uses those timestamps to resolve conflicts, implementations should generate
* monotonically increasing timestamps for successive invocations of {@link TimestampGenerator.next()}.
* </p>
* @constructor
*/
function TimestampGenerator() {
}
/**
* Returns the next timestamp.
* <p>
* Implementors should enforce increasing monotonicity of timestamps, that is,
* a timestamp returned should always be strictly greater that any previously returned
* timestamp.
* <p/>
* <p>
* Implementors should strive to achieve microsecond precision in the best possible way,
* which is usually largely dependent on the underlying operating system's capabilities.
* </p>
* @param {Client} client The {@link Client} instance to generate timestamps to.
* @returns {Long|Number|null} the next timestamp (in microseconds). If it's equals to <code>null</code>, it won't be
* sent by the driver, letting the server to generate the timestamp.
* @abstract
*/
TimestampGenerator.prototype.next = function (client) {
throw new Error('next() must be implemented');
};
/**
* A timestamp generator that guarantees monotonically increasing timestamps and logs warnings when timestamps
* drift in the future.
* <p>
* {@link Date} has millisecond precision and client timestamps require microsecond precision. This generator
* keeps track of the last generated timestamp, and if the current time is within the same millisecond as the last,
* it fills the microsecond portion of the new timestamp with the value of an incrementing counter.
* </p>
* @param {Number} [warningThreshold] Determines how far in the future timestamps are allowed to drift before a
* warning is logged, expressed in milliseconds. Default: <code>1000</code>.
* @param {Number} [minLogInterval] In case of multiple log events, it determines the time separation between log
* events, expressed in milliseconds. Use 0 to disable. Default: <code>1000</code>.
* @extends {TimestampGenerator}
* @constructor
*/
function MonotonicTimestampGenerator(warningThreshold, minLogInterval) {
if (warningThreshold < 0) {
throw new errors.ArgumentError('warningThreshold can not be lower than 0');
}
this._warningThreshold = warningThreshold || 1000;
this._minLogInterval = 1000;
if (typeof minLogInterval === 'number') {
// A value under 1 will disable logging
this._minLogInterval = minLogInterval;
}
this._micros = -1;
this._lastDate = 0;
this._lastLogDate = 0;
}
util.inherits(MonotonicTimestampGenerator, TimestampGenerator);
/**
* Returns the current time in milliseconds since UNIX epoch
* @returns {Number}
*/
MonotonicTimestampGenerator.prototype.getDate = function () {
return Date.now();
};
MonotonicTimestampGenerator.prototype.next = function (client) {
let date = this.getDate();
let drifted = 0;
if (date > this._lastDate) {
this._micros = 0;
this._lastDate = date;
return this._generateMicroseconds();
}
if (date < this._lastDate) {
drifted = this._lastDate - date;
date = this._lastDate;
}
if (++this._micros === 1000) {
this._micros = 0;
if (date === this._lastDate) {
// Move date 1 millisecond into the future
date++;
drifted++;
}
}
const lastDate = this._lastDate;
this._lastDate = date;
const result = this._generateMicroseconds();
if (drifted >= this._warningThreshold) {
// Avoid logging an unbounded amount of times within a clock-skew event or during an interval when more than 1
// query is being issued by microsecond
const currentLogDate = Date.now();
if (this._minLogInterval > 0 && this._lastLogDate + this._minLogInterval <= currentLogDate){
const message = util.format(
'Timestamp generated using current date was %d milliseconds behind the last generated timestamp (which ' +
'millisecond portion was %d), the returned value (%s) is being artificially incremented to guarantee ' +
'monotonicity.',
drifted, lastDate, result);
this._lastLogDate = currentLogDate;
client.log('warning', message);
}
}
return result;
};
/**
* @private
* @returns {Number|Long}
*/
MonotonicTimestampGenerator.prototype._generateMicroseconds = function () {
if (this._lastDate < _maxSafeNumberDate) {
// We are safe until Jun 06 2255, its faster to perform this operations on Number than on Long
// We hope to have native int64 by then :)
return this._lastDate * 1000 + this._micros;
}
return Long
.fromNumber(this._lastDate)
.multiply(_longOneThousand)
.add(Long.fromInt(this._micros));
};
exports.TimestampGenerator = TimestampGenerator;
exports.MonotonicTimestampGenerator = MonotonicTimestampGenerator;