UNPKG

aerospike

Version:
626 lines (594 loc) 23.5 kB
// ***************************************************************************** // Copyright 2013-2024 Aerospike, 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 policy = require('./policy') const inspect = Symbol.for('nodejs.util.inspect.custom') class Config { /** * @class Config * @classdesc config command. * The Config class contains the settings for an Aerospike client * instance, including the list of seed hosts, default policies, and other * settings. * * @type Class * @throws {TypeError} If invalid config values are passed. * * @example * * const Aerospike = require('aerospike') * * let config = { * hosts: '192.168.1.10,192.168.1.11', * user: process.env.DATABASE_USER, * password: process.env.DATABASE_PASSWORD, * policies: { * read: new Aerospike.ReadPolicy({ * totalTimeout: 0 * }) * }, * log: { * level: Aerospike.log.INFO, * file: 2 // log to stderr * } * } * * Aerospike.connect(config) * .then(client => { * // client is ready to accept commands * client.close() * }) * .catch(error => { * console.error('Failed to connect to cluster: %s', error.message) * }) * * * // Initializes a new client configuration from the given config values. * * @param {Object} [config] configuration values */ constructor (config) { config = config || {} /** * @name Config#user * @summary The user name to use when authenticating to the cluster. * @description Leave empty for clusters running without access management. * (Security features are available in the Aerospike Database Enterprise * Edition.) * @type {string} */ if (typeof config.user === 'string') { this.user = config.user } /** * @name Config#password * @summary The password to use when authenticating to the cluster. * @type {string} */ if (typeof config.password === 'string') { this.password = config.password } /** * @name Config#authMode * @summary Authentication mode used when user/password is defined. * @description One of the auth modes defined in {@link module:aerospike.auth}. * @type {number} * @see module:aerospike.auth */ if (typeof config.authMode === 'number') { this.authMode = config.authMode } /** * @name Config#clusterName * @summary Expected Cluster Name. * @description If not <code>null</code>, server nodes must return this * cluster name in order to join the client's view of the cluster. Should * only be set when connecting to servers that support the "cluster-name" * info command. * @type {string} * @since v2.4 */ this.clusterName = config.clusterName /** * @name Config#port * @summary Default port to use for any host address, that does not * explicitly specify a port number. Default is 3000. * @type {number} * * @since v2.4 */ if (typeof config.port === 'number') { this.port = config.port } else { this.port = 3000 } /** * @name Config#tls * @summary Configure Transport Layer Security (TLS) parameters for secure * connections to the database cluster. TLS connections are not supported as * of Aerospike Server v3.9 and depend on a future server release. * @type {Object} * @since v2.4 * * @property {boolean} [enable=true] - Enable TLS for socket connections to * cluster nodes. By default TLS is enabled only if the client configuration * includes a <code>tls</code> section. * @property {string} [cafile] - Path to a trusted CA certificate file. By * default TLS will use system standard trusted CA certificates. * @property {string} [capath] - Path to a directory of trusted certificates. * See the OpenSSL SSL_CTX_load_verify_locations manual page for more * information about the format of the directory. * @property {string} [protocols] - Specifies enabled protocols. The format is * the same as Apache's SSLProtocol documented at * https://httpd.apache.org/docs/current/mod/mod_ssl.html#sslprotocol. If not * specified, the client will use "-all +TLSv1.2". If you are not sure what * protocols to select this option is best left unspecified. * @property {string} [cipherSuite] - Specifies enabled cipher suites. The * format is the same as OpenSSL's Cipher List Format documented at * https://www.openssl.org/docs/manmaster/apps/ciphers.html. If not specified * the OpenSSL default cipher suite described in the ciphers documentation * will be used. If you are not sure what cipher suite to select this option * is best left unspecified. * @property {string} [certBlacklist] - Path to a certificate blacklist file. * The file should contain one line for each blacklisted certificate. Each * line starts with the certificate serial number expressed in hex. Each * entry may optionally specify the issuer name of the certificate. (Serial * numbers are only required to be unique per issuer.) Example records: * <code><br>867EC87482B2 /C=US/ST=CA/O=Acme/OU=Engineering/CN=Test Chain CA<br> * E2D4B0E570F9EF8E885C065899886461</code> * @property {string} [keyfile] - Path to the client's key for mutual * authentication. By default, mutual authentication is disabled. * @property {string} [keyfilePassword] - Decryption password for the * client's key for mutual authentication. By default, the key is assumed * not to be encrypted. * @property {string} [certfile] - Path to the client's certificate chain * file for mutual authentication. By default, mutual authentication is * disabled. * @property {boolean} [crlCheck=false] - Enable CRL checking for the * certificate chain leaf certificate. An error occurs if a suitable CRL * cannot be found. By default CRL checking is disabled. * @property {boolean} [crlCheckAll=false] - Enable CRL checking for the * entire certificate chain. An error occurs if a suitable CRL cannot be * found. By default CRL checking is disabled. * @property {boolean} [logSessionInfo=false] - Log session information for * each connection. * @property {boolean} [forLoginOnly=false] - Use TLS connections only for login * authentication. All other communication with the server will be done * with non-TLS connections. Default: false (Use TLS connections for all * communication with the server.) */ if (typeof config.tls === 'object') { this.tls = config.tls } /** * @summary List of hosts with which the client should attempt to connect. * @description If not specified, the client attempts to read the host list * from the <code>AEROSPIKE_HOSTS</code> environment variable or else falls * back to use a default value of "localhost". * @type {(Host[] | string)} * * @example <caption>Setting <code>hosts</code> using a string:</caption> * * const Aerospike = require('aerospike') * * const hosts = '192.168.0.1:3000,192.168.0.2:3000' * const client = await Aerospike.connect({ hosts }) * * @example <caption>Setting <code>hosts</code> using an array of hostname/port tuples:</caption> * * const Aerospike = require('aerospike') * * const hosts = [ * { addr: '192.168.0.1', port: 3000 }, * { addr: '192.168.0.2', port: 3000 } * ] * const client = await Aerospike.connect({ hosts }) * * @example <caption>Setting <code>hosts</code> with TLS name using a string:</caption> * * const Aerospike = require('aerospike') * * const hosts = '192.168.0.1:example.com:3000,192.168.0.2:example.com:3000' * const client = await Aerospike.connect({ hosts }) * * @example <caption>Setting <code>hosts</code> using an array of hostname/port/tlsname tuples:</caption> * * const Aerospike = require('aerospike') * * const hosts = [ * { addr: '192.168.0.1', port: 3000, tlsname: 'example.com' }, * { addr: '192.168.0.2', port: 3000, tlsname: 'example.com' } * ] * const client = await Aerospike.connect({ hosts }) */ this.hosts = config.hosts || process.env.AEROSPIKE_HOSTS || `localhost:${this.port}` /** * @summary Global client policies. * * @description The configuration defines default policies for the * application. Policies define the behavior of the client, which can be * global for all uses of a single type of operation, or local to a single * use of an operation. * * Each database operation accepts a policy for that operation as an * argument. This is considered a local policy, and is a single use policy. * This local policy supersedes any global policy defined. * * If a value of the policy is not defined, then the rule is to fallback to * the global policy for that operation. If the global policy for that * operation is undefined, then the global default value will be used. * * If you find that you have behavior that you want every use of an * operation to utilize, then you can specify the default policy as * {@link Config#policies}. * * For example, the {@link Client#put} operation takes a {@link * WritePolicy} parameter. If you find yourself setting the {@link * WritePolicy#key} policy value for every call to {@link Client.put}, then * you may find it beneficial to set the global {@link WritePolicy} in * {@link Config#policies}, which all operations will use. * * @type {Policies} * * @example <caption>Setting a default <code>key</code> policy for all write operations</caption> * * const Aerospike = require('aerospike') * * // INSERT HOSTNAME AND PORT NUMBER OF AEROSPIKE SERVER NODE HERE! * var config = { * hosts: '192.168.33.10:3000', * policies: { * write: new Aerospike.WritePolicy({ * key: Aerospike.policy.key.SEND, * socketTimeout : 0, * totalTimeout : 0 * }) * } * } * * let key = new Aerospike.Key('test', 'demo', 123) * * Aerospike.connect(config) * .then(client => { * return client.put(key, {int: 42}) * .then(() => client.close()) * .catch(error => { * throw error * client.close() * }) * }) * .catch(console.error) */ this.policies = {} if (typeof config.policies === 'object') { this.setDefaultPolicies(config.policies) } /** * @name Config#log * @summary Configuration for logging done by the client. * @type {Object} * * @property {Number} [log.level] - Log level; see {@link * module:aerospike.log} for details. * @property {Number} [log.file] - File descriptor returned by * <code>fs.open()</code> or one of <code>process.stdout.fd</code> or * <code>process.stderr.fd</code>. * * @example <caption>Enabling debug logging to a separate log file</caption> * * const Aerospike = require('aerospike') * * const fs = require('fs') * * var debuglog = fs.openSync('./debug.log', 'w') * // INSERT HOSTNAME AND PORT NUMBER OF AEROSPIKE SERVER NODE HERE! * var config = { * hosts: '192.168.33.10:3000', * log: { * level: Aerospike.log.DEBUG, * file: debuglog * } * } * Aerospike.connect(config, (err, client) => { * if (err) throw err * console.log("Connected. Now closing connection.") * client.close() * }) */ if (typeof config.log === 'object') { this.log = config.log } /** * @name Config#connTimeoutMs * @summary Initial host connection timeout in milliseconds. * @description The client observes this timeout when opening a connection to * the cluster for the first time. * @type {number} * @default 1000 */ if (Number.isInteger(config.connTimeoutMs)) { this.connTimeoutMs = config.connTimeoutMs } /** * @name Config#loginTimeoutMs * @summary Node login timeout in milliseconds. * @type {number} * @default 5000 */ if (Number.isInteger(config.loginTimeoutMs)) { this.loginTimeoutMs = config.loginTimeoutMs } /** * @name Config#maxSocketIdle * * @summary Maximum socket idle time in seconds. * * @description Connection pools will discard sockets that have been idle * longer than the maximum. The value is limited to 24 hours (86400). * * It's important to set this value to a few seconds less than the server's * <code>proto-fd-idle-ms</code> (default 60000 milliseconds or 1 minute), * so the client does not attempt to use a socket that has already been * reaped by the server. * * Connection pools are now implemented by a LIFO stack. Connections at the * tail of the stack will always be the least used. These connections are * checked for <code>maxSocketIdle</code> once every 30 tend iterations * (usually 30 seconds). * * @type {number} * * @default 0 seconds */ if (Number.isInteger(config.maxSocketIdle)) { this.maxSocketIdle = config.maxSocketIdle } /** * @name Config#tenderInterval * @summary Polling interval in milliseconds for cluster tender. * @type {number} * @default 1000 */ if (Number.isInteger(config.tenderInterval)) { this.tenderInterval = config.tenderInterval } /** * @name Config#maxConnsPerNode * * @summary Maximum number of asynchronous connections allowed per server node. * * @description New transactions will be rejected with an {@link * module:aerospike/status.ERR_NO_MORE_CONNECTIONS|ERR_NO_MORE_CONNECTIONS} * error if the limit would be exceeded. * * @type {number} * * @default 100 */ if (Number.isInteger(config.maxConnsPerNode)) { this.maxConnsPerNode = config.maxConnsPerNode } /** * @name Config#maxErrorRate * * @summary Maximum number of errors allowed per node per error_rate_window before backoff algorithm returns * AEROSPIKE_MAX_ERROR_RATE for database commands to that node. If max_error_rate is zero, there is no error limit. * The counted error types are any error that causes the connection to close (socket errors and client timeouts), * server device overload and server timeouts. * * The application should backoff or reduce the transaction load until AEROSPIKE_MAX_ERROR_RATE stops being returned. * * @description If the backoff algorithm has been activated, transactions will fail with {@link * module:aerospike/status.AEROSPIKE_MAX_ERROR_RATE|AEROSPIKE_MAX_ERROR_RATE} until the {@link Config#errorRateWindow} has passed and the * error count has been reset. * * @type {number} * * @default 100 */ if (Number.isInteger(config.maxErrorRate)) { this.maxErrorRate = config.maxErrorRate } /** * @name Config#errorRateWindow * * @summary The number of cluster tend iterations that defines the window for {@link Config#maxErrorRate} to be surpassed. One tend iteration is defined * as {@link Config#tendInterval} plus the time to tend all nodes. At the end of the window, the error count is reset to zero and backoff state is removed on all nodes. * * @type {number} * * @default 1 */ if (Number.isInteger(config.errorRateWindow)) { this.errorRateWindow = config.errorRateWindow } /** * @name Config#minConnsPerNode * * @summary Minimum number of asynchronous connections allowed per server node. * * @description Preallocate min connections on client node creation. The * client will periodically allocate new connections if count falls below * min connections. * * Server <code>proto-fd-idle-ms</code> may also need to be increased * substantially if min connections are defined. The * <code>proto-fd-idle-ms</code> default directs the server to close * connections that are idle for 60 seconds which can defeat the purpose of * keeping connections in reserve for a future burst of activity. * * If server <code>proto-fd-idle-ms</code> is changed, client {@link * Config#maxSocketIdle} should also be changed to be a few seconds less * than <code>proto-fd-idle-ms</code>. * * @type {number} * @default 0 */ if (Number.isInteger(config.minConnsPerNode)) { this.minConnsPerNode = config.minConnsPerNode } if (typeof config.modlua === 'object') { /** * @summary Configuration values for the mod-lua user path. * @description If you are using user-defined functions (UDF) for processing * query results (i.e. aggregations), then you will find it useful to set * the <code>modlua</code> settings. Of particular importance is the * <code>modelua.userPath</code>, which allows you to define a path to where * the client library will look for Lua files for processing. * @type {Object} * * @property {string} [modlua.userPath] - Path to user Lua scripts. */ this.modlua = Object.assign({}, config.modlua) } /** * @name Config#sharedMemory * @summary Shared memory configuration. * @description This allows multiple client instances running in separate * processes on the same machine to share cluster status, including nodes and * data partition maps. Each shared memory segment contains state for one * Aerospike cluster. If there are multiple Aerospike clusters, a different * <code>key</code> must be defined for each cluster. * @type {Object} * @see {@link http://www.aerospike.com/docs/client/c/usage/shm.html#operational-notes|Operational Notes} * @tutorial node_clusters * * @property {boolean} [enable=true] - Whether to enable/disable usage of * shared memory. * @property {number} key - Identifier for the shared memory segment * associated with the target Aerospike cluster; the same key needs to be * used on all client instances connecting to the same cluster. * @property {number} [maxNodes=16] - Sets the max. number of * server nodes in the cluster - this value is required to size the shared * memory segment. Ensure that you leave a cushion between actual server node * cound and <code>maxNodes</code> so that you can add new nodes without * rebooting the client. * @property {number} [maxNamespaces=8] - Sets the max. number of * namespaces used in the cluster - this value is required to size the shared * memory segment. Ensure that you leave a cushion between actual namespace * count and <code>maxNamespaces</code> so that you can add new namespaces * without rebooking the client. * @property {number} [takeoverThresholdSeconds=30] - Expiration * time in seconds for the lock on the shared memory segment; if the cluster * status has not been updated after this many seconds another client instance * will take over the shared memory cluster tending. * * @example <caption>Using shared memory in a clustered setup</caption> * * const Aerospike = require('aerospike') * const cluster = require('cluster') * * const config = { * sharedMemory: { * key: 0xa5000000 * } * } * const client = Aerospike.client(config) * const noWorkers = 4 * * if (cluster.isMaster) { * // spawn new worker processes * for (var i = 0; i < noWorkers; i++) { * cluster.fork() * } * } else { * // connect to Aerospike cluster in each worker process * client.connect((err) => { if (err) throw err }) * * // handle incoming HTTP requests, etc. * // http.createServer((request, response) => { ... }) * * // close DB connection on shutdown * client.close() * } */ if (typeof config.sharedMemory === 'object') { this.sharedMemory = config.sharedMemory } /** * @name Config#useAlternateAccessAddress * @summary Whether the client should use the server's * <code>alternate-access-address</code> instead of the * <code>access-address</code>. * * @type {boolean} * @default false * @since v3.7.1 */ this.useAlternateAccessAddress = Boolean(config.useAlternateAccessAddress) /** * @name Config#rackAware * @summary Track server rack data. * @description This field is useful when directing read commands to the * server node that contains the key and exists on the same rack as the * client. This serves to lower cloud provider costs when nodes are * distributed across different racks/data centers. * * {@link Config#rackId rackId} config, {@link * module:aerospike/policy.replica PREFER_RACK} replica policy, and server * rack configuration must also be set to enable this functionality. * * @type {boolean} * @default false * @since 3.8.0 */ this.rackAware = config.rackAware /** * @name Config#rackId * @summary Rack where this client instance resides. * @description {@link Config#rackAware rackAware} config, {@link * module:aerospike/policy.replica PREFER_RACK} replica policy, and server * rack configuration must also be set to enable this functionality. * * @type {number} * @default 0 * @since 3.8.0 */ if (Number.isInteger(config.rackId)) { this.rackId = config.rackId } this.rackIds = config.rackIds this.configProvider = config.configProvider this.appId = config.appId } /** * Set default policies from the given policy values. * * @param {Policies} one or more default policies * @throws {TypeError} if any of the properties of the policies object is not * a valid policy type */ setDefaultPolicies (policies) { for (const type in policies) { const values = policies[type] this.policies[type] = policy.createPolicy(type, values) } } /** * Custom inspector that masks the password property when printing the * config. * * @private */ [inspect] () { const copy = Object.assign({}, this) if (this.password !== undefined) { Object.assign(copy, { password: '[FILTERED]' }) } return copy } } module.exports = Config