Comparing version
@@ -6,3 +6,3 @@ /* | ||
* | ||
* Copyright (c) 2016, Joyent, Inc. | ||
* Copyright 2017 Joyent, Inc. | ||
*/ | ||
@@ -25,3 +25,2 @@ | ||
const mod_resolver = require('./resolver'); | ||
const mod_uuid = require('node-uuid'); | ||
const mod_errors = require('./errors'); | ||
@@ -341,3 +340,3 @@ | ||
SocketMgrFSM.prototype.state_backoff = function (S) { | ||
S.validTransitions(['failed', 'connecting']); | ||
S.validTransitions(['failed', 'connecting', 'closed']); | ||
@@ -367,14 +366,6 @@ /* | ||
var t = S.timeout(delay, function () { | ||
S.timeout(delay, function () { | ||
S.gotoState('connecting'); | ||
}); | ||
/* | ||
* If we're in monitor mode, unref the backoff timer. That way we | ||
* don't block a commandline app from exiting that wants to exit as | ||
* soon as a pool enters 'failed'. | ||
*/ | ||
if (this.sm_monitor) | ||
t.unref(); | ||
S.on(this, 'closeAsserted', function () { | ||
@@ -962,12 +953,2 @@ S.gotoState('closed'); | ||
if (smgr.isInState('connected')) { | ||
var sock = smgr.getSocket(); | ||
/* | ||
* If we can, unref the socket while idle, so that a bunch of | ||
* spare connections doing nothing don't hold the process open. | ||
*/ | ||
if (typeof (sock.unref) === 'function') | ||
sock.unref(); | ||
} | ||
/* Monitor successfully connected: make it into a normal slot now. */ | ||
@@ -1097,4 +1078,2 @@ if (this.csf_monitor === true) { | ||
var sock = smgr.getSocket(); | ||
if (typeof (sock.ref) === 'function') | ||
sock.ref(); | ||
hdl.accept(sock); | ||
@@ -1101,0 +1080,0 @@ } else { |
@@ -6,3 +6,3 @@ /* | ||
* | ||
* Copyright (c) 2016, Joyent, Inc. | ||
* Copyright 2017 Joyent, Inc. | ||
*/ | ||
@@ -23,3 +23,3 @@ | ||
const mod_resolver = require('./resolver'); | ||
const mod_uuid = require('node-uuid'); | ||
const mod_uuid = require('uuid'); | ||
const mod_errors = require('./errors'); | ||
@@ -26,0 +26,0 @@ |
@@ -920,6 +920,5 @@ /* | ||
'sleeping until next TTL expiry'); | ||
var t = S.timeout(minDelay, function () { | ||
S.timeout(minDelay, function () { | ||
S.gotoState(state); | ||
}); | ||
t.unref(); | ||
S.on(this, 'stopAsserted', function () { | ||
@@ -926,0 +925,0 @@ S.gotoState('init'); |
@@ -6,3 +6,3 @@ /* | ||
* | ||
* Copyright (c) 2016, Joyent, Inc. | ||
* Copyright 2017 Joyent, Inc. | ||
*/ | ||
@@ -23,3 +23,3 @@ | ||
const mod_resolver = require('./resolver'); | ||
const mod_uuid = require('node-uuid'); | ||
const mod_uuid = require('uuid'); | ||
const mod_errors = require('./errors'); | ||
@@ -26,0 +26,0 @@ const mod_monitor = require('./pool-monitor'); |
{ | ||
"name": "cueball", | ||
"version": "2.0.1", | ||
"description": "", | ||
"version": "2.1.0", | ||
"description": "manage a pool of connections to a multi-node service where nodes are listed in DNS", | ||
"main": "lib/index.js", | ||
@@ -12,7 +12,6 @@ "dependencies": { | ||
"ipaddr.js": ">=1.1.0 <2.0.0", | ||
"mooremachine": ">=2.0.0 <3.0.0", | ||
"mooremachine": ">=2.1.0 <3.0.0", | ||
"mname-client": ">=0.5.0 <0.6.0", | ||
"node-uuid": ">=1.4.7 <2.0.0", | ||
"uuid": ">=3.0.1 <4.0.0", | ||
"posix-getopt": ">=1.2.0 <2.0.0", | ||
"restify-clients": ">=1.1.2 <2.0.0", | ||
"vasync": ">=1.6.3 <2.0.0", | ||
@@ -31,2 +30,5 @@ "verror": ">=1.6.1 <2.0.0" | ||
}, | ||
"engines": { | ||
"node": ">=0.10.0" | ||
}, | ||
"scripts": { | ||
@@ -33,0 +35,0 @@ "test": "tape test/*.test.js" |
778
README.md
@@ -1,772 +0,12 @@ | ||
cueball | ||
======= | ||
# cueball | ||
`cueball` is a node.js library for "playing pool" -- managing a pool of | ||
connections to a multi-node service where nodes are listed in DNS. | ||
<!-- | ||
This file is here because npm doesn't support any format except markdown for | ||
README files. This way people browsing the package on npmjs.com will get a | ||
link to the documentation instead of nothing. | ||
It supports DNS SRV record style services, as well as the simpler kind with | ||
multiple A/AAAA records under the same name, correctly respecting record | ||
TTLs and making use of Additional sections where possible. | ||
GitHub prefers .adoc files to .md, so it will render the correct README for | ||
the repository root. | ||
--> | ||
The library also includes an HTTP `Agent` which can be used with the regular | ||
node `http` client stack, including libraries like `restify` which layer on | ||
top of it. The `Agent` transparently creates pools for services as you make | ||
requests to connect to them. | ||
Install | ||
------- | ||
``` | ||
npm install cueball | ||
``` | ||
Example | ||
------- | ||
Using the `HttpsAgent`: | ||
```js | ||
const mod_cueball = require('cueball'); | ||
const mod_restify = require('restify-clients'); | ||
var client = mod_restify.createStringClient({ | ||
url: 'https://us-east.manta.joyent.com', | ||
agent: new mod_cueball.HttpsAgent({ | ||
spares: 4, maximum: 10, | ||
recovery: { | ||
default: { | ||
timeout: 2000, | ||
retries: 5, | ||
delay: 250, | ||
maxDelay: 1000 | ||
} | ||
} | ||
}) | ||
}); | ||
client.get('/foobar/public', function (err, req, res, data) { | ||
... | ||
}); | ||
``` | ||
This will create a connection pool that aims to keep 4 spare connections at | ||
all times, up to a maximum of 10 total connections, for | ||
`us-east.manta.joyent.com`. | ||
It will respect DNS TTLs and automatically rebalance the pool as IP addresses | ||
are added to or removed from DNS. | ||
API | ||
--- | ||
## Agent | ||
### `new mod_cueball.HttpAgent(options)` | ||
### `new mod_cueball.HttpsAgent(options)` | ||
Creates an HTTP(S) agent that can be used with the node `http` client API. | ||
Parameters | ||
- `options` -- Object, with keys: | ||
- `recovery` -- Object, a recovery spec (see below) | ||
- `spares` -- Number, number of spares wanted in the pool per host | ||
- `maximum` -- Number, maximum number of connections per host | ||
- `resolvers` -- optional Array of String, either containing IP addresses to | ||
use as nameservers, or a single string for Dynamic Resolver mode | ||
- `log` -- optional Object, a `bunyan`-style logger to use | ||
- `initialDomains` -- optional Array of String, initial domains to create | ||
connections to at startup (to pre-seed the Agent for quick user later) | ||
- `defaultPort` -- optional Number, fallback TCP port to connect to (default | ||
80 for HttpAgent, 443 for HttpsAgent). If SRV records for a name are found | ||
the port from SRV will always be used instead of this. | ||
- `tcpKeepAliveInitialDelay` -- optional Number, if supplied, enable TCP | ||
level keep-alives with the given initial delay (in milliseconds) | ||
- `ping` -- optional String, URL path to use for health checking. Connection | ||
is considered still viable if this URL returns a non-5xx response code. | ||
- `pingInterval` -- optional Number, interval between health check pings | ||
- `errorOnEmpty` -- optional Boolean | ||
### HttpAgent#stop([cb]) | ||
Stops the pools managed by this agent, calling `cb` (if given) once all have | ||
stopped. | ||
Once an Agent has been stopped, it can no longer accept any new requests, and | ||
will throw an Error if asked to do so. Calling `stop()` more than once is | ||
also an error and will throw. | ||
Parameters | ||
- `cb` -- optional Function (err) | ||
## Pool | ||
### `new mod_cueball.ConnectionPool(options)` | ||
Creates a new pool of connections. There are two ways of using a | ||
ConnectionPool. You can either provide your own resolver directly, or provide | ||
parameters with which to create the default, DNS-based resolver. | ||
Parameters | ||
- `options` -- Object, with keys: | ||
- `constructor` -- Function(backend) -> object, must open a new connection | ||
to the given backend and return it | ||
- `domain` -- String, name to look up to find backends. | ||
- `recovery` -- Object, a recovery spec (see below) | ||
- `spares` -- Number, number of spares wanted in the pool per host | ||
- `maximum` -- Number, maximum number of connections per host | ||
- `service` -- optional String, name of SRV service (e.g. `_http._tcp`) | ||
- `defaultPort` -- optional Number, port to use for plain A/AAAA records | ||
- `resolvers` -- optional Array of String, either containing IP addresses to | ||
use as nameservers, or a single string for Dynamic Resolver mode (default | ||
uses system resolvers from `/etc/resolv.conf`) | ||
- `log` -- optional Object, a `bunyan`-style logger to use | ||
- `maxDNSConcurrency` -- optional Number, max number of DNS queries to issue | ||
at once (default 5) | ||
- `checkTimeout` -- optional Number, milliseconds of idle time before | ||
running `checker` on a connection | ||
- `checker` -- optional Function(handle, connection), to be run on idle | ||
connections | ||
- `resolver` -- optional instance of an object meeting the Resolver interface | ||
below. You would typically obtain this object by either creating your own | ||
Resolver directly or using the `resolverForIpOrDomain` function. | ||
Do not confuse `resolvers` (the list of IP addresses for the DNS resolvers to | ||
contact) with `resolver` (a custom object meeting the Resolver interface below). | ||
If you want to use a custom resolver, then you must specify the `resolver` | ||
property. In that case, the `resolvers`, `maxDNSConcurrency`, `defaultPort`, | ||
and `recovery` options are ignored, and the `domain` and `service` properties | ||
are used only for logging. | ||
Otherwise, if want to use the default DNS-based resolver, do not specify the | ||
`resolver` property. A resolver instance will be created based on the other | ||
configuration properties. | ||
### Pool states | ||
ConnectionPool exposes the `mooremachine` FSM interface, with the following | ||
state graph: | ||
| (from failed) | ||
.stop() v | ||
+--------+ connect ok +-------+ +--------+ | ||
init --> |starting| +------------> |running| +---> |stopping| | ||
+--------+ +-------+ +--------+ | ||
+ ^ + + | ||
resolver | | | | | ||
failed | | | | | ||
OR | +------+ | | v | ||
retries +----> |failed| +-----+ | +-------+ | ||
exhausted +------+ connect ok | |stopped| | ||
+ ^ | +-------+ | ||
| | | | ||
.stop()| +----------------+ | ||
| all retries exhausted | ||
Pools begin their life in the "starting" state. Once they have successfully made | ||
one connection to any backend, they proceed to the "running" state. Otherwise, | ||
if their underlying Resolver enters the "failed" state, or they exhaust their | ||
retry policy attempting to connect to all their backends, they enter the | ||
"failed" state. | ||
A "running" pool can then either be stopped by calling the `.stop()` method, at | ||
which point it enters the "stopping" state and begins tearing down its | ||
connections; or all of its connections become disconnected and it exhausts its | ||
retry policy, in which case it enters the "failed" state. | ||
Failed pools can re-enter the "running" state at any time if they make a | ||
successful connection to a backend and their underlying Resolver is no longer | ||
"failed". A "failed" pool can also have the `.stop()` method called, in which | ||
case it proceeds much as from "running". | ||
### `ConnectionPool#stop()` | ||
Stops the connection pool and its `Resolver`, then destroys all connections. | ||
### `ConnectionPool#claim([options, ]callback)` | ||
Claims a connection from the pool ready for use. | ||
Parameters | ||
- `options` -- optional Object, with keys: | ||
- `timeout` -- optional Number, timeout for request in ms | ||
(default `Infinity`) | ||
- `errorOnEmpty` -- optional Boolean, if true return error straight away | ||
if the pool has no backends at all (i.e., nothing was found in DNS) | ||
- `callback` -- Function(err[, handle, connection]), parameters: | ||
- `err` -- an Error object, if the request could not be fulfilled or timed | ||
out | ||
- `handle` -- Object, handle to be used to release the connection back to | ||
the pool when work is complete | ||
- `connection` -- Object, the actual connection (as returned by the | ||
`constructor` given to `new ConnectionPool()`) | ||
Returns a "waiter handle", which is an Object having a `cancel()` method. The | ||
`cancel()` method may be called at any time up to when the `callback` is run, to | ||
cancel the request to the pool and relinquish any queue positions held. | ||
When a client is done with a connection, they must call `handle.release()` to | ||
return it to the pool. All event handlers should be disconnected from the | ||
`connection` prior to calling `release()`. | ||
If a client determines that a connection must be closed immediately (e.g. due | ||
to a protocol error making it impossible to continue using it safely), it must | ||
call the `.close()` method on the *handle*, not any `.destroy()` or similar | ||
method on the connection itself. | ||
Calling `claim()` on a Pool that is in the "stopping", "stopped" or "failed" | ||
states will result in the callback being called with an error on the next run of | ||
the event loop. | ||
### `ConnectionPool#claimSync()` | ||
Claims a connection from the pool, only if an idle one is immediately | ||
available. Otherwise, throws an Error. Always throws an Error if called on a | ||
Pool that is "stopping", "stopped" or "failed". | ||
Returns an Object with keys: | ||
- `handle` -- Object, handle to be used to release the connection | ||
- `connection` -- Object, actual connection | ||
## Resolver | ||
### `mod_cueball.Resolver` interface | ||
An interface for all "resolvers", objects which take in some kind of | ||
configuration (e.g. a DNS name) and track a list of "backends" for that | ||
name. A "backend" is an IP/port pair that describes an endpoint that can | ||
be connected to to reach a given service. | ||
Resolver exposes the `mooremachine` FSM interface, with the following state | ||
graph: | ||
.start() error | ||
+-------+ +--------+ +------+ | ||
init -> |stopped| +---> |starting| +---> |failed| | ||
+---+---+ +---+----+ +------+ | ||
^ | + | ||
| | ok | | ||
| v | | ||
+---+----+ +---+---+ | | ||
|stopping| <--+ |running| <--------+ | ||
+--------+ +-------+ retry success | ||
.stop() | ||
Resolvers begin their life "stopped". When the user calls `.start()`, they | ||
begin the process of resolving the name/configuration they were given into | ||
backends. | ||
If the initial attempt to resolve the name/configuration fails, the Resolver | ||
enters the "failed" state, but continues retrying. If it succeeds, or if any | ||
later retry succeeds, it moves to the "running" state. The reason why the | ||
"failed" state exists is so that commandline tools and other short-lived | ||
processes can make use of it to decide when to "give up" on a name resolution. | ||
Once an attempt has succeeded, the Resolver will begin emitting `added` and | ||
`removed` events (see below) describing the backends that it has found. | ||
In the "running" state, the Resolver continues to monitor the source of its | ||
backends (e.g. in DNS by retrying once the TTL expires) and emitting these | ||
events when changes occur. | ||
Finally, when the `.stop()` method is called, the Resolver transitions to | ||
"stopping", stops monitoring and emitting events, and comes to rest in the | ||
"stopped" state where it started. | ||
### `Resolver#start()` | ||
Starts the resolver's normal operation (by beginning the process of looking up | ||
the names given). | ||
### `Resolver#stop()` | ||
Stops the resolver. No further events will be emitted unless `start()` is | ||
called again. | ||
### `Resolver#getLastError()` | ||
Returns the last error experienced by the Resolver. This is particularly useful | ||
when the Resolver is in the "failed" state, to produce a log message or user | ||
interface text. | ||
### `Resolver#getState()` | ||
Returns the current state of the Resolver as a string (see diagram above). | ||
Inherited from `mod_mooremachine.FSM`. | ||
### `Resolver#onState(state, cb)` | ||
Registers an event handler to run when the Resolver enters the given state. | ||
Inherited from `mod_mooremachine.FSM`. | ||
### Event `Resolver->added(key, backend)` | ||
Emitted when a new backend has been found. | ||
Parameters | ||
- `key` -- String, a unique key for this backend (will be referenced by any | ||
subsequent events about this backend) | ||
- `backend` -- Object, with keys: | ||
- `name` -- String, the DNS name for this backend | ||
- `address` -- String, an IPv4 or IPv6 address | ||
- `port` -- Number | ||
### Event `Resolver->removed(key)` | ||
Emitted when an existing backend has been removed. | ||
Parameters | ||
- `key` -- String, unique key for this backend | ||
## DNS-based name resolver | ||
### `new mod_cueball.DNSResolver(options)` | ||
Creates a Resolver that looks up a name in DNS. This Resolver prefers SRV | ||
records if they are available, and falls back to A/AAAA records if they cannot | ||
be found. | ||
Parameters | ||
- `options` -- Object, with keys: | ||
- `domain` -- String, name to look up to find backends | ||
- `recovery` -- Object, a recovery spec (see below) | ||
- `service` -- optional String, name of SRV service (e.g. `_http._tcp`) | ||
- `defaultPort` -- optional Number, port to use for plain A/AAAA records | ||
- `resolvers` -- optional Array of String, either containing IP addresses to | ||
use as nameservers, or a single string for Dynamic Resolver mode (default | ||
uses system resolvers from `/etc/resolv.conf`) | ||
- `log` -- optional Object, a `bunyan`-style logger to use | ||
- `maxDNSConcurrency` -- optional Number, max number of DNS queries to issue | ||
at once (default 5) | ||
## Static IP resolver | ||
### `new mod_cueball.StaticIpResolver(options)` | ||
Creates a new static IP resolver. This object matches the Resolver interface | ||
above, but emits a fixed list of IP addresses when started. This list never | ||
changes. This is intended for development environments and debugging tools, | ||
where a user may have provided an explicit IP address rather than a DNS name to | ||
contact. See also: `resolverForIpOrDomain()`. | ||
Parameters | ||
- `options` -- Object, with keys: | ||
- `defaultPort` -- optional Number, fallback port to use for backends | ||
that only have an `address` property | ||
- `backends` -- Array of objects, each having properties: | ||
- `address` -- String, an IP address to emit as a backend | ||
- `port` -- Number (optional if `defaultPort` used), a port number | ||
for this backend | ||
This object provides the same `start()` and `stop()` methods as the Resolver | ||
class, as well as the same `added` and `removed` events. | ||
## Picking the right resolver | ||
### `resolverForIpOrDomain(options)` | ||
Services that use DNS for service discovery would typically use a DNS-based | ||
resolver. But in development environments or with debugging tools, it's useful | ||
to be able to point a cueball-using program at an instance located at a specific | ||
IP address and port. That's what the Static IP resolver is for. | ||
To make this easy for programs that want to support connecting to either | ||
hostnames or IP addresses, this function is provided to take configuration | ||
(expected to come from a user, via an environment variable, command-line | ||
option, or other configuration source), determine whether an IP address or DNS | ||
name was specified, and return either a DNS-based or static resolver. If the | ||
input appears to be neither a valid IPv4 nor IPv6 address nor DNS name, or the | ||
port number is not valid, then an Error is returned (not thrown). (If the | ||
input is missing or has the wrong type, an Error object is thrown, since this | ||
is a programmer error.) | ||
Parameters | ||
- `options` -- Object, with keys: | ||
- `input` -- String, either an IP address or DNS name, with optional port | ||
suffix | ||
- `resolverConfig` -- Object, a set of additional properties to pass to | ||
the resolver constructor, with keys: | ||
- `defaultPort` -- optional Number, used for both DNS and static names | ||
- `recovery` -- Object, see `DNSResolver`, required for DNS lookups | ||
- `service` -- optional String, see `DNSResolver` | ||
- `resolvers` -- optional Array of String, see `DNSResolver` | ||
- `log` -- optional Object, a `bunyan`-style logger to use | ||
The `input` string has the form `HOSTNAME[:PORT]`, where the `[:PORT]` suffix is | ||
optional, and `HOSTNAME` may be either an IP address or DNS name. | ||
**Example:** create a resolver that will emit one backend for an instance at IP | ||
127.0.0.1 port 2020: | ||
var resolver = mod_cueball.resolverForIpOrDomain({ | ||
'input': '127.0.0.1:2020', | ||
'resolverConfig': { | ||
'recovery': { | ||
'default': { | ||
'retries': 1, | ||
'timeout': 1000, | ||
'delay': 1000, | ||
'maxDelay': 1000 | ||
} | ||
} | ||
} | ||
}) | ||
/* check whether resolver is an Error */ | ||
**Example:** create a resolver that will track instances associated with DNS | ||
name `mydomain.example.com`: | ||
var resolver = mod_cueball.resolverForIpOrDomain({ | ||
'input': 'mydomain.example.com', | ||
'resolverConfig': { | ||
'recovery': { | ||
'default': { | ||
'retries': 1, | ||
'timeout': 1000, | ||
'delay': 1000, | ||
'maxDelay': 1000 | ||
} | ||
} | ||
} | ||
}); | ||
/* check whether resolver is an Error */ | ||
In these examples, the `input` string is assumed to come from a user | ||
cueball does the expected thing when given an IP address or DNS name. | ||
## Connection interface | ||
Objects returned by a `constructor` function (such as supplied to the | ||
`ConnectionPool` constructor) must obey a subset of the node.js socket | ||
interface. In particular they must support the following events and methods: | ||
### `constructor(backend)` | ||
Parameters: | ||
- `backend` -- an Object, with properties: | ||
- `key` -- a String, the backend key as supplied via the Resolver interface. | ||
Can be used to uniquely identify the backend. | ||
- `address` -- a String, address of the backend (IPv4 or IPv6) | ||
- `port` -- a Number, TCP or UDP port number | ||
Returns an object obeying the Connection interface. | ||
### Event `connect` (required) | ||
At construction, the connection object must immediately attempt to make a | ||
connection to the backend specified by the first argument to the constructor. | ||
When the connection succeeds, it must emit the event `connect`. No arguments are | ||
required. | ||
### Event `error` | ||
Connection objects may emit `error` at any time in response to a fatal error. | ||
The connection will be immediately terminated (by calling `.destroy()`) upon the | ||
emission of any `error` event. | ||
The `error` event should be emitted with an Object as the first parameter. This | ||
is expected to have `Error` on its prototype chain (`obj instanceof Error` | ||
should be `true`). | ||
May also be emitted as `connectError` only in the state before `connect` has | ||
been emitted. | ||
### Event `close` (required) | ||
Connection objects must emit `close` as the final event they emit after the | ||
connection has ended. No events may be emitted after `close`. | ||
### `#ref()` (required) | ||
Marks the connection as "referenced", meaning that it should keep the Node | ||
process running even if no event handlers are registered on it. | ||
It is generally acceptable for long-lived server processes (which never intend | ||
to exit in normal operation) to provide a Connection that implements `ref()` and | ||
`unref()` as no-ops. | ||
### `#unref()` (required) | ||
Marks the connection as "unreferenced", allowing the Node process to exit if it | ||
is the only remaining source of events. | ||
### `#destroy()` (required) | ||
Immediately disconnects the connection and proceeds to emit `close`. | ||
### Event `timeout` | ||
Optional. Equivalent to emitting `error` with a ConnectionTimeoutError as an | ||
argument. | ||
May also be emitted as `connectTimeout` only in the state before `connect` has | ||
been emitted. | ||
## Errors | ||
### `ClaimTimeoutError` | ||
Passed as first argument to `ConnectionPool#claim()`'s callback when the given | ||
timeout in `options` has been exceeded. | ||
Properties | ||
- `pool` -- ConnectionPool | ||
### `NoBackendsError` | ||
Passed as first argument to `ConnectionPool#claim()`'s callback when there are | ||
no known backends for the pool and the `errorOnEmpty` flag is set. | ||
Properties | ||
- `pool` -- ConnectionPool | ||
## Kang support | ||
### `mod_cueball.poolMonitor.toKangOptions()` | ||
Returns an options object that can be passed to `mod_kang.knStartServer`. The | ||
kang options set up snapshots containing a list of all `Pool` objects in the | ||
system and their associated backends and state. | ||
The returned object is missing the `port` property, which should be added | ||
before using. | ||
## Recovery objects | ||
To specify the retry and timeout behaviour of Cueball DNS and pooled | ||
connections, the "recovery spec object" is a required argument to most | ||
constructors in the API. | ||
A recovery spec object should always have at least one key, named `"default"`, | ||
which gives the default settings for any operation. | ||
More specific per-operation settings can also be given as additional keys. | ||
For example: | ||
```js | ||
{ | ||
default: { | ||
timeout: 2000, | ||
retries: 3, | ||
delay: 100 | ||
}, | ||
dns: { | ||
timeout: 5000, | ||
retries: 3, | ||
delay: 200 | ||
} | ||
} | ||
``` | ||
This specifies that DNS-related operations should have a timeout of 5 seconds, | ||
3 retries, and initial delay of 200ms, while all other operations (e.g. | ||
`connect()` while connecting to a new backend) should have a timeout of 2 | ||
seconds, 3 retries and initial delay of 100ms. | ||
The `delay` field indicates a time to wait between retry attempts. After each | ||
failure, it will be doubled until it exceeds the value of `maxDelay`. | ||
The possible fields in one operation are: | ||
- `retries` finite Number >= 0, number of retry attempts | ||
- `timeout` finite Number > 0, milliseconds to wait before declaring an | ||
attempt a failure | ||
- `maxTimeout` Number > `timeout` (can be `Infinity`), maximum value of | ||
`timeout` to be reached with exponential timeout increase | ||
- `delay` finite Number >= 0, milliseconds to delay between retry attempts | ||
- `maxDelay` Number > `delay` (can be `Infinity`), maximum value of `delay` | ||
to be reached with exponential delay increase | ||
And the available operations: | ||
- `dns` (all DNS-related operations, lookups etc) | ||
- `dns_srv` (specifically lookups on SRV records, this is separate in case you | ||
need to deal with certain old buggy DNS servers that have trouble with SRV) | ||
- `connect` (connections to backends in a `ConnectionPool`) | ||
- `initial` (the very first attempt to connect to a new backend, will fall | ||
back to `connect` if not given) | ||
If a given operation has no specification given, it will use `default` instead. | ||
Dynamic Resolver mode | ||
--------------------- | ||
`Resolver` instances can operate in a so-called "Dynamic Resolver" mode, where | ||
as well as tracking their particular target service in DNS, they also track the | ||
correct nameservers to ask about it. | ||
This is useful in systems where the nameservers are listed in DNS as a service | ||
just like your ordinary target service (e.g. HTTP). An example is the Joyent | ||
SDC `binder`. `binder` acts as a DNS server, listing addresses of all SDC | ||
service instances. This includes listing its own address, and if multiple | ||
`binder`s are deployed, all other `binder`s in the DC. | ||
We can look up the list of currently available `binder` instances in DNS, and | ||
use this to perform our name resolution. We can also then use the `binder`s to | ||
update our original list of `binder` instances. | ||
This mode requires a "bootstrap" to begin with, however -- we cannot resolve | ||
the name that the `binder` instances are listed under until we already know the | ||
address of one of the `binder`s. In Dynamic Resolver mode, `cueball` will | ||
bootstrap using the system resolvers from `/etc/resolv.conf`. | ||
### Example | ||
```js | ||
const mod_cueball = require('cueball'); | ||
const mod_restify = require('restify-clients'); | ||
var client = mod_restify.createJsonClient({ | ||
url: 'http://napi.coal.joyent.us', | ||
agent: new mod_cueball.HttpAgent({ | ||
resolvers: ['binder.coal.joyent.us'], | ||
spares: 4, maximum: 8 | ||
}) | ||
}); | ||
client.get('/networks/' + uuid, function (err, req, res, data) { | ||
... | ||
}); | ||
``` | ||
This example code will start by using the system resolvers to resolve | ||
`binder.coal.joyent.us`. Then, the records found via this lookup will be used | ||
as nameservers to look up `napi.coal.joyent.us`. | ||
When the TTL expires on the records for `binder.coal.joyent.us`, we will use | ||
the records from the previous lookup as the list of nameservers to query in | ||
order to find out what the new records should be. Then, we will use any new | ||
nameservers we find for the next `napi.coal.joyent.us` lookup as well. | ||
ConnectionSet | ||
------------- | ||
Cueball also includes an alternative to the ConnectionPool, named ConnectionSet. | ||
This is a more low-level API which is useful for implementing clients for | ||
protocols that are not as strictly connection-oriented. | ||
Key differences to ConnectionPool: | ||
- Each backend in a ConnectionSet has a maximum of 1 connection open to it | ||
(it's expected to be used with protocols that multiplex operations over a | ||
single socket.) | ||
- No support for leases (claim/release). ConnectionSet does not track whether | ||
connections are busy or not, and expects its consumer to manage this. | ||
ConnectionSets have an identical state graph to ConnectionPools. | ||
### `new mod_cueball.ConnectionSet(options)` | ||
Parameters | ||
- `options` -- Object, with keys: | ||
- `resolver` -- Object, an instance of the Resolver interface | ||
- `constructor` -- Function, same as in ConnectionPool | ||
- `recovery` -- Object, a recovery spec (see below) | ||
- `target` -- Number, target number of connections to be made | ||
available in the entire set | ||
- `maximum` -- Number, maximum number of sockets opened by the set. | ||
Note that this number may temporarily be exceeded by 1 socket | ||
to allow the set to re-balance. | ||
- `log` -- optional Object, a `bunyan`-style logger to use | ||
### Event `'added'` | ||
Emitted when a new connection becomes available in the set. This event *must* | ||
have a handler on it at all times. | ||
The `handle` that is given as the third argument to this event has two methods | ||
`.release()` and `.close()`, like a Pool handle. As with Pool handles, it can | ||
be used to indicate the failure of a connection (e.g. due to a protocol error | ||
making safe use of the connection impossible) at any time, but unlike a Pool | ||
handle, it is an error to call `.release()` until after a `'removed'` event | ||
has been emitted. | ||
The user of the ConnectionSet should store both the `connection` and `handle` | ||
in such a way as to be able to retrieve them using the `key`. | ||
Parameters | ||
- `key` -- String, a unique key to identify this connection | ||
- `connection` -- Object, the connection as returned by the constructor | ||
- `handle` -- Object, a handle to be used in response to a 'removed' event | ||
about this connection | ||
### Event `removed` | ||
Emitted when an existing connection should be removed from the pool. This event | ||
*must* have a handler on it at all times. The handler is obligated to take all | ||
necessary actions to drain the connection of outstanding requests and then | ||
call the `.release()` method on the relevant handle. | ||
Parameters | ||
- `key` -- String, a unique key to identify the connection | ||
### `ConnectionSet#stop()` | ||
Stops the ConnectionSet, disconnecting all available connections (by first | ||
emitting `'removed'` for them.) | ||
### `ConnectionSet#setTarget(target)` | ||
Sets the target number of connections in the ConnectionSet. Will trigger an | ||
async operation to add or remove connections in order to meet the new target. | ||
Parameters: | ||
- `target` -- Number | ||
### `ConnectionSet#getConnections()` | ||
Returns all the currently open connections in the Set, as an Array. | ||
Tools | ||
----- | ||
The `cbresolve` tool is provided to show how cueball would resolve a given | ||
configuration. The output format is not committed. It may change in the | ||
future. | ||
usage: cbresolve HOSTNAME[:PORT] # for DNS-based lookup | ||
cbresolve -S | --static IP[:PORT]... # for static IPs | ||
Locate services in DNS using Cueball resolver. | ||
The following options are available for DNS-based lookups: | ||
-f, --follow periodically re-resolve and report changes | ||
-p, --port PORT default backend port | ||
-r, --resolvers IP[,IP...] list of DNS resolvers | ||
-s, --service SERVICE "service" name (for SRV) | ||
-t, --timeout TIMEOUT timeout for lookups | ||
**Example:** resolve DNS name "1.moray.us-east.joyent.us": | ||
$ cbresolve 1.moray.emy-10.joyent.us | ||
domain: 1.moray.emy-10.joyent.us | ||
timeout: 5000 milliseconds | ||
172.27.10.218 80 lLbminikNKjfy+iwDobYBuod7Hs= | ||
172.27.10.219 80 iJMaVRehJ2zKfiS55H/lUUFPb9o= | ||
**Example:** resolve IP/port "127.0.0.1:2020". This is only useful for seeing | ||
how cueball would parse your input: | ||
$ cbresolve --static 127.0.0.1:2020 | ||
using static IP resolver | ||
127.0.0.1 2020 xBut/f1D52k1TpDN/miW82qXw6k= | ||
**Example: resolve DNS name "1.moray.us-east.joyent.us" and watch for changes: | ||
$ cbresolve --follow 1.moray.emy-10.joyent.us | ||
domain: 1.moray.emy-10.joyent.us | ||
timeout: 5000 milliseconds | ||
2016-06-23T00:45:00.312Z added 172.27.10.218:80 (lLbminikNKjfy+iwDobYBuod7Hs=) | ||
2016-06-23T00:45:00.314Z added 172.27.10.219:80 (iJMaVRehJ2zKfiS55H/lUUFPb9o=) | ||
2016-06-23T00:49:00.478Z removed 172.27.10.218:80 (lLbminikNKjfy+iwDobYBuod7Hs=) | ||
In this example, one of the DNS entries was removed a few minutes after the | ||
program was started. | ||
See the [documentation](https://joyent.github.io/node-cueball). |
Sorry, the diff of this file is not supported yet
12
-7.69%17
6.25%160995
-13.13%4448
-0.45%13
-98.32%+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated