Compare commits
73 Commits
release-0.
...
v0.2.19
Author | SHA1 | Date | |
---|---|---|---|
6159cd56b8 | |||
cbf3c7bb5e | |||
b17ab0c77a | |||
c67d0d25a2 | |||
5bc2aaa093 | |||
401f6c3190 | |||
40736d9da3 | |||
853ae875b8 | |||
da6d059547 | |||
e9834daa31 | |||
f79a0444f6 | |||
eb56e077f9 | |||
11f882501f | |||
7b7ccc2eef | |||
6b75d241fc | |||
7da398eae8 | |||
195a6d40ce | |||
55b9ddd856 | |||
fe82aebfec | |||
3f7798d46e | |||
6ad2d34114 | |||
67dab18d53 | |||
78677ba53e | |||
bbf25ee8b5 | |||
732b691e40 | |||
941f671ed2 | |||
343eaa85df | |||
e0fcf9a72d | |||
02642c5cfb | |||
d64f181dbb | |||
9e31b17c44 | |||
4ae4c1e010 | |||
76a405ace2 | |||
930b900426 | |||
934251d7fa | |||
1449b43b8d | |||
2f4850680f | |||
8f68923008 | |||
8e54a22cd2 | |||
6663b42903 | |||
ad8d16d112 | |||
bebd04a398 | |||
4d981596eb | |||
7ccced45aa | |||
88df807508 | |||
35f720f94f | |||
6d49f51e7d | |||
d8baed4c5d | |||
7e5327c278 | |||
bb53c01fe8 | |||
20818869f1 | |||
16f172c6f2 | |||
7db060af6e | |||
2c272a19f5 | |||
7dc4658719 | |||
811368c1af | |||
fcf27fba20 | |||
e9d86a3c4d | |||
0db2b93b79 | |||
ef26ed636f | |||
2893454e8a | |||
a0642ab081 | |||
760d31cc31 | |||
c1f17a7d99 | |||
96145fbe49 | |||
5ac4a206ad | |||
e1f99cecbc | |||
0a64b37b6f | |||
3421d41068 | |||
215708014c | |||
c3a63ff8b7 | |||
d8c0bfb03a | |||
34e8262dc8 |
11
.idea/codeStyles/Project.xml
generated
Normal file
11
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="JavaScriptSettings">
|
<component name="JavaScriptSettings">
|
||||||
<option name="languageLevel" value="ES5" />
|
<option name="languageLevel" value="ES6" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
44
README.md
44
README.md
@ -1,6 +1,10 @@
|
|||||||
# node-eventstore-client
|
# node-eventstore-client
|
||||||
A port of the EventStore .Net ClientAPI to Node.js
|
A port of the EventStore .Net ClientAPI to Node.js
|
||||||
|
|
||||||
|
## Learning
|
||||||
|
|
||||||
|
If you want to learn more about EventSourcing/EventModeling, you can join one of the monthly virtual workshops offered by my employer Adaptech Group, see info at [https://adaptechgroup.com/#workshop](https://adaptechgroup.com/#workshop).
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
### Missing features:
|
### Missing features:
|
||||||
@ -23,7 +27,7 @@ Install using `npm install node-eventstore-client`
|
|||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
- Node.js >= 4.0
|
- Node.js >= 4.0
|
||||||
- Modules: [long](https://www.npmjs.org/package/long), [protobufjs](https://www.npmjs.org/package/protobufjs), [uuid](https://www.npmjs.org/package/uuid) (installed via `npm install`)
|
- Modules: [long](https://www.npmjs.org/package/long), [protobufjs](https://www.npmjs.org/package/protobufjs), [uuid](https://www.npmjs.org/package/uuid), [strict-event-emitter-types](https://www.npmjs.com/package/strict-event-emitter-types) (installed via `npm install`)
|
||||||
|
|
||||||
### Install and run an Eventstore on localhost
|
### Install and run an Eventstore on localhost
|
||||||
|
|
||||||
@ -112,12 +116,27 @@ To generate a test event, open a separate console and run:
|
|||||||
|
|
||||||
## Running the tests
|
## Running the tests
|
||||||
|
|
||||||
|
### Local testing
|
||||||
|
|
||||||
To run the tests it is recommended that you use an in-memory instance of the eventstore so you don't pollute your dev instance.
|
To run the tests it is recommended that you use an in-memory instance of the eventstore so you don't pollute your dev instance.
|
||||||
|
|
||||||
EventStore.ClusterNode.exe --run-projections=all --memdb –certificate-file=yourcert.pfx
|
EventStore.ClusterNode.exe --run-projections=all --memdb –certificate-file=yourcert.pfx
|
||||||
or
|
or
|
||||||
./run-node.sh --run-projections=all --memdb –certificate-file=yourcert.p12
|
./run-node.sh --run-projections=all --memdb –certificate-file=yourcert.p12
|
||||||
|
|
||||||
|
You can also use docker-compose :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# start the single node cluster
|
||||||
|
npm run compose:single:start
|
||||||
|
# if you want to wait for the cluster to be available
|
||||||
|
npm run compose:wait
|
||||||
|
# run the tests
|
||||||
|
npm run test
|
||||||
|
# to cleanup (stop containres, delete volumes)
|
||||||
|
npm run compose:single:stop
|
||||||
|
```
|
||||||
|
|
||||||
For SSL setup see:
|
For SSL setup see:
|
||||||
|
|
||||||
https://eventstore.org/docs/server/setting_up_ssl/
|
https://eventstore.org/docs/server/setting_up_ssl/
|
||||||
@ -128,6 +147,29 @@ To execute the tests suites simply run
|
|||||||
|
|
||||||
npm test
|
npm test
|
||||||
|
|
||||||
|
### Isolated environment
|
||||||
|
|
||||||
|
To be able to run the tests for different connection types (tcp, gossip, cluster) docker-compose files are available to setup the environment and run the tests.
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
* docker
|
||||||
|
* docker-compose
|
||||||
|
|
||||||
|
#### Run
|
||||||
|
|
||||||
|
To execute the tests suites for single node cluster (tcp connection) simply run
|
||||||
|
|
||||||
|
npm run test:single
|
||||||
|
|
||||||
|
To execute the tests suites for multiple nodes cluster (gossip connection) simply run
|
||||||
|
|
||||||
|
npm run test:gossip
|
||||||
|
|
||||||
|
To execute the tests suites for multiple nodes cluster (dns discovery connection) simply run
|
||||||
|
|
||||||
|
npm run test:cluster
|
||||||
|
|
||||||
## Porting .Net Task to Node.js
|
## Porting .Net Task to Node.js
|
||||||
|
|
||||||
Any async commands returns a [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) object in replacement of .Net Task.
|
Any async commands returns a [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) object in replacement of .Net Task.
|
||||||
|
147
docker-compose-cluster.yaml
Normal file
147
docker-compose-cluster.yaml
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
eventstore.db:
|
||||||
|
image: eventstore/eventstore:release-5.0.8
|
||||||
|
environment:
|
||||||
|
- EVENTSTORE_WORKER_THREADS=5
|
||||||
|
- EVENTSTORE_CLUSTER_SIZE=3
|
||||||
|
- EVENTSTORE_RUN_PROJECTIONS=All
|
||||||
|
- EVENTSTORE_DB=/var/lib/eventstore-data
|
||||||
|
- EVENTSTORE_INDEX=/var/lib/eventstore-index
|
||||||
|
- EVENTSTORE_LOG=/var/log/eventstore
|
||||||
|
- EVENTSTORE_CLUSTER_GOSSIP_PORT=2112
|
||||||
|
- EVENTSTORE_INT_TCP_PORT=1112
|
||||||
|
- EVENTSTORE_EXT_TCP_PORT=1113
|
||||||
|
- EVENTSTORE_INT_HTTP_PORT=2112
|
||||||
|
- EVENTSTORE_EXT_HTTP_PORT=2113
|
||||||
|
- EVENTSTORE_DISCOVER_VIA_DNS=true
|
||||||
|
- EVENTSTORE_CLUSTER_DNS=eventstore.local
|
||||||
|
- EVENTSTORE_INT_IP=192.168.33.10
|
||||||
|
- EVENTSTORE_EXT_IP=192.168.33.10
|
||||||
|
ports:
|
||||||
|
- "1112:1112"
|
||||||
|
- "1113:1113"
|
||||||
|
- "2112:2112"
|
||||||
|
- "2113:2113"
|
||||||
|
networks:
|
||||||
|
app_net:
|
||||||
|
aliases:
|
||||||
|
- eventstore.local
|
||||||
|
ipv4_address: 192.168.33.10
|
||||||
|
volumes:
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-data
|
||||||
|
target: /var/lib/eventstore-data
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-index
|
||||||
|
target: /var/lib/eventstore-index
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-logs
|
||||||
|
target: /var/log/eventstore
|
||||||
|
|
||||||
|
eventstore.db2:
|
||||||
|
image: eventstore/eventstore:release-5.0.8
|
||||||
|
environment:
|
||||||
|
- EVENTSTORE_WORKER_THREADS=5
|
||||||
|
- EVENTSTORE_CLUSTER_SIZE=3
|
||||||
|
- EVENTSTORE_RUN_PROJECTIONS=All
|
||||||
|
- EVENTSTORE_DB=/var/lib/eventstore-data
|
||||||
|
- EVENTSTORE_INDEX=/var/lib/eventstore-index
|
||||||
|
- EVENTSTORE_LOG=/var/log/eventstore
|
||||||
|
- EVENTSTORE_CLUSTER_GOSSIP_PORT=2112
|
||||||
|
- EVENTSTORE_INT_TCP_PORT=1112
|
||||||
|
- EVENTSTORE_EXT_TCP_PORT=1113
|
||||||
|
- EVENTSTORE_INT_HTTP_PORT=2112
|
||||||
|
- EVENTSTORE_EXT_HTTP_PORT=2113
|
||||||
|
- EVENTSTORE_DISCOVER_VIA_DNS=true
|
||||||
|
- EVENTSTORE_CLUSTER_DNS=eventstore.local
|
||||||
|
- EVENTSTORE_INT_IP=192.168.33.11
|
||||||
|
- EVENTSTORE_EXT_IP=192.168.33.11
|
||||||
|
expose:
|
||||||
|
- "1113"
|
||||||
|
- "1112"
|
||||||
|
- "2112"
|
||||||
|
- "2113"
|
||||||
|
networks:
|
||||||
|
app_net:
|
||||||
|
aliases:
|
||||||
|
- eventstore.local
|
||||||
|
ipv4_address: 192.168.33.11
|
||||||
|
volumes:
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-data2
|
||||||
|
target: /var/lib/eventstore-data
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-index2
|
||||||
|
target: /var/lib/eventstore-index
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-logs2
|
||||||
|
target: /var/log/eventstore
|
||||||
|
|
||||||
|
eventstore.db3:
|
||||||
|
image: eventstore/eventstore:release-5.0.8
|
||||||
|
environment:
|
||||||
|
- EVENTSTORE_WORKER_THREADS=5
|
||||||
|
- EVENTSTORE_CLUSTER_SIZE=3
|
||||||
|
- EVENTSTORE_RUN_PROJECTIONS=All
|
||||||
|
- EVENTSTORE_DB=/var/lib/eventstore-data
|
||||||
|
- EVENTSTORE_INDEX=/var/lib/eventstore-index
|
||||||
|
- EVENTSTORE_LOG=/var/log/eventstore
|
||||||
|
- EVENTSTORE_CLUSTER_GOSSIP_PORT=2112
|
||||||
|
- EVENTSTORE_INT_TCP_PORT=1112
|
||||||
|
- EVENTSTORE_EXT_TCP_PORT=1113
|
||||||
|
- EVENTSTORE_INT_HTTP_PORT=2112
|
||||||
|
- EVENTSTORE_EXT_HTTP_PORT=2113
|
||||||
|
- EVENTSTORE_DISCOVER_VIA_DNS=true
|
||||||
|
- EVENTSTORE_CLUSTER_DNS=eventstore.local
|
||||||
|
- EVENTSTORE_INT_IP=192.168.33.12
|
||||||
|
- EVENTSTORE_EXT_IP=192.168.33.12
|
||||||
|
expose:
|
||||||
|
- "1113"
|
||||||
|
- "1112"
|
||||||
|
- "2112"
|
||||||
|
- "2113"
|
||||||
|
networks:
|
||||||
|
app_net:
|
||||||
|
aliases:
|
||||||
|
- eventstore.local
|
||||||
|
ipv4_address: 192.168.33.12
|
||||||
|
volumes:
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-data3
|
||||||
|
target: /var/lib/eventstore-data
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-index3
|
||||||
|
target: /var/lib/eventstore-index
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-logs3
|
||||||
|
target: /var/log/eventstore
|
||||||
|
|
||||||
|
nodejs:
|
||||||
|
image: node:14
|
||||||
|
working_dir: /var/code
|
||||||
|
volumes:
|
||||||
|
- .:/var/code
|
||||||
|
- /var/code/node_modules
|
||||||
|
command: bash -c "tail -f /dev/null"
|
||||||
|
networks:
|
||||||
|
- app_net
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
eventstore-volume-data:
|
||||||
|
eventstore-volume-index:
|
||||||
|
eventstore-volume-logs:
|
||||||
|
eventstore-volume-data2:
|
||||||
|
eventstore-volume-index2:
|
||||||
|
eventstore-volume-logs2:
|
||||||
|
eventstore-volume-data3:
|
||||||
|
eventstore-volume-index3:
|
||||||
|
eventstore-volume-logs3:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app_net:
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: "192.168.33.0/24"
|
54
docker-compose-single.yaml
Normal file
54
docker-compose-single.yaml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
eventstore.db:
|
||||||
|
image: eventstore/eventstore:release-5.0.8
|
||||||
|
environment:
|
||||||
|
- EVENTSTORE_CLUSTER_SIZE=1
|
||||||
|
- EVENTSTORE_RUN_PROJECTIONS=All
|
||||||
|
- EVENTSTORE_START_STANDARD_PROJECTIONS=True
|
||||||
|
- EVENTSTORE_DB=/var/lib/eventstore-data
|
||||||
|
- EVENTSTORE_INDEX=/var/lib/eventstore-index
|
||||||
|
- EVENTSTORE_LOG=/var/log/eventstore
|
||||||
|
- EVENTSTORE_EXT_TCP_PORT=1113
|
||||||
|
- EVENTSTORE_EXT_HTTP_PORT=2113
|
||||||
|
- EVENTSTORE_INT_IP=192.168.33.10
|
||||||
|
- EVENTSTORE_EXT_IP=192.168.33.10
|
||||||
|
ports:
|
||||||
|
- "1113:1113"
|
||||||
|
- "2113:2113"
|
||||||
|
volumes:
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-data
|
||||||
|
target: /var/lib/eventstore-data
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-index
|
||||||
|
target: /var/lib/eventstore-index
|
||||||
|
- type: volume
|
||||||
|
source: eventstore-volume-logs
|
||||||
|
target: /var/log/eventstore
|
||||||
|
networks:
|
||||||
|
app_net:
|
||||||
|
ipv4_address: 192.168.33.10
|
||||||
|
|
||||||
|
nodejs:
|
||||||
|
image: node:14
|
||||||
|
working_dir: /var/code
|
||||||
|
volumes:
|
||||||
|
- .:/var/code
|
||||||
|
- /var/code/node_modules
|
||||||
|
command: bash -c "tail -f /dev/null"
|
||||||
|
networks:
|
||||||
|
- app_net
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
eventstore-volume-data:
|
||||||
|
eventstore-volume-index:
|
||||||
|
eventstore-volume-logs:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app_net:
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: "192.168.33.0/24"
|
31
index.d.ts
vendored
31
index.d.ts
vendored
@ -1,6 +1,9 @@
|
|||||||
/// <reference types="node" />
|
/// <reference types="node" />
|
||||||
/// <reference types="Long" />
|
/// <reference types="Long" />
|
||||||
|
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { StrictEventEmitter } from 'strict-event-emitter-types';
|
||||||
|
|
||||||
// Expose classes
|
// Expose classes
|
||||||
export class Position {
|
export class Position {
|
||||||
constructor(commitPosition: number|Long, preparePosition: number|Long);
|
constructor(commitPosition: number|Long, preparePosition: number|Long);
|
||||||
@ -69,6 +72,7 @@ export class ProjectionsManager {
|
|||||||
enable(name: string, userCredentials: UserCredentials): Promise<void>;
|
enable(name: string, userCredentials: UserCredentials): Promise<void>;
|
||||||
disable(name: string, userCredentials: UserCredentials): Promise<void>;
|
disable(name: string, userCredentials: UserCredentials): Promise<void>;
|
||||||
abort(name: string, userCredentials: UserCredentials): Promise<void>;
|
abort(name: string, userCredentials: UserCredentials): Promise<void>;
|
||||||
|
reset(name: string, userCredentials: UserCredentials): Promise<void>;
|
||||||
createOneTime(query: string, userCredentials: UserCredentials): Promise<void>;
|
createOneTime(query: string, userCredentials: UserCredentials): Promise<void>;
|
||||||
createTransient(name: string, query: string, userCredentials: UserCredentials): Promise<void>;
|
createTransient(name: string, query: string, userCredentials: UserCredentials): Promise<void>;
|
||||||
createContinuous(name: string, query: string, trackEmittedStreams: boolean, userCredentials: UserCredentials): Promise<void>;
|
createContinuous(name: string, query: string, trackEmittedStreams: boolean, userCredentials: UserCredentials): Promise<void>;
|
||||||
@ -84,7 +88,7 @@ export class ProjectionsManager {
|
|||||||
getQuery(name: string, userCredentials: UserCredentials): Promise<string>;
|
getQuery(name: string, userCredentials: UserCredentials): Promise<string>;
|
||||||
getState(name: string, userCredentials: UserCredentials): Promise<string>;
|
getState(name: string, userCredentials: UserCredentials): Promise<string>;
|
||||||
updateQuery(name: string, query: string, userCredentials: UserCredentials): Promise<void>;
|
updateQuery(name: string, query: string, userCredentials: UserCredentials): Promise<void>;
|
||||||
deleteQuery(name: string, deleteEmittedStreams: boolean, userCredentials: UserCredentials): Promise<void>;
|
delete(name: string, deleteEmittedStreams: boolean, deleteStateStream: boolean, deleteCheckpointStream: boolean, userCredentials: UserCredentials): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose errors
|
// Expose errors
|
||||||
@ -121,6 +125,7 @@ export namespace expectedVersion {
|
|||||||
const any: number;
|
const any: number;
|
||||||
const noStream: number;
|
const noStream: number;
|
||||||
const emptyStream: number;
|
const emptyStream: number;
|
||||||
|
const streamExists: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace positions {
|
export namespace positions {
|
||||||
@ -170,6 +175,7 @@ export interface RecordedEvent {
|
|||||||
readonly eventId: string;
|
readonly eventId: string;
|
||||||
readonly eventNumber: Long;
|
readonly eventNumber: Long;
|
||||||
readonly eventType: string;
|
readonly eventType: string;
|
||||||
|
readonly created: Date;
|
||||||
readonly createdEpoch: number;
|
readonly createdEpoch: number;
|
||||||
readonly data?: Buffer;
|
readonly data?: Buffer;
|
||||||
readonly metadata?: Buffer;
|
readonly metadata?: Buffer;
|
||||||
@ -305,7 +311,18 @@ export interface EventData {
|
|||||||
readonly metadata: Buffer;
|
readonly metadata: Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventStoreNodeConnection {
|
interface EventStoreNodeConnectionEvents {
|
||||||
|
connected: TcpEndPoint;
|
||||||
|
disconnected: TcpEndPoint;
|
||||||
|
reconnecting: void;
|
||||||
|
closed:string;
|
||||||
|
error: Error;
|
||||||
|
heartbeatInfo: HeartbeatInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventStoreNodeConnectionEventEmitter = StrictEventEmitter<EventEmitter, EventStoreNodeConnectionEvents>;
|
||||||
|
|
||||||
|
export class EventStoreNodeConnection extends (EventEmitter as { new(): EventStoreNodeConnectionEventEmitter }) {
|
||||||
connect(): Promise<void>;
|
connect(): Promise<void>;
|
||||||
close(): void;
|
close(): void;
|
||||||
// write actions
|
// write actions
|
||||||
@ -325,16 +342,13 @@ export interface EventStoreNodeConnection {
|
|||||||
subscribeToAll(resolveLinkTos: boolean, eventAppeared: EventAppearedCallback<EventStoreSubscription>, subscriptionDropped?: SubscriptionDroppedCallback<EventStoreSubscription>, userCredentials?: UserCredentials): Promise<EventStoreSubscription>;
|
subscribeToAll(resolveLinkTos: boolean, eventAppeared: EventAppearedCallback<EventStoreSubscription>, subscriptionDropped?: SubscriptionDroppedCallback<EventStoreSubscription>, userCredentials?: UserCredentials): Promise<EventStoreSubscription>;
|
||||||
subscribeToAllFrom(lastCheckpoint: Position|null, resolveLinkTos: boolean, eventAppeared: EventAppearedCallback<EventStoreCatchUpSubscription>, liveProcessingStarted?: LiveProcessingStartedCallback, subscriptionDropped?: SubscriptionDroppedCallback<EventStoreCatchUpSubscription>, userCredentials?: UserCredentials, readBatchSize?: number): EventStoreCatchUpSubscription;
|
subscribeToAllFrom(lastCheckpoint: Position|null, resolveLinkTos: boolean, eventAppeared: EventAppearedCallback<EventStoreCatchUpSubscription>, liveProcessingStarted?: LiveProcessingStartedCallback, subscriptionDropped?: SubscriptionDroppedCallback<EventStoreCatchUpSubscription>, userCredentials?: UserCredentials, readBatchSize?: number): EventStoreCatchUpSubscription;
|
||||||
// persistent subscriptions
|
// persistent subscriptions
|
||||||
createPersistentSubscription(stream: string, groupName: string, settings: PersistentSubscriptionSettings, userCredentials?: PersistentSubscriptionSettings): Promise<PersistentSubscriptionCreateResult>;
|
createPersistentSubscription(stream: string, groupName: string, settings: PersistentSubscriptionSettings, userCredentials?: UserCredentials): Promise<PersistentSubscriptionCreateResult>;
|
||||||
updatePersistentSubscription(stream: string, groupName: string, settings: PersistentSubscriptionSettings, userCredentials?: PersistentSubscriptionSettings): Promise<PersistentSubscriptionUpdateResult>;
|
updatePersistentSubscription(stream: string, groupName: string, settings: PersistentSubscriptionSettings, userCredentials?: UserCredentials): Promise<PersistentSubscriptionUpdateResult>;
|
||||||
deletePersistentSubscription(stream: string, groupName: string, userCredentials?: PersistentSubscriptionSettings): Promise<PersistentSubscriptionDeleteResult>
|
deletePersistentSubscription(stream: string, groupName: string, userCredentials?: UserCredentials): Promise<PersistentSubscriptionDeleteResult>
|
||||||
connectToPersistentSubscription(stream: string, groupName: string, eventAppeared: EventAppearedCallback<EventStorePersistentSubscription>, subscriptionDropped?: SubscriptionDroppedCallback<EventStorePersistentSubscription>, userCredentials?: UserCredentials, bufferSize?: number, autoAck?: boolean): Promise<EventStorePersistentSubscription>;
|
connectToPersistentSubscription(stream: string, groupName: string, eventAppeared: EventAppearedCallback<EventStorePersistentSubscription>, subscriptionDropped?: SubscriptionDroppedCallback<EventStorePersistentSubscription>, userCredentials?: UserCredentials, bufferSize?: number, autoAck?: boolean): Promise<EventStorePersistentSubscription>;
|
||||||
// metadata actions
|
// metadata actions
|
||||||
setStreamMetadataRaw(stream: string, expectedMetastreamVersion: Long|number, metadata: any, userCredentials?: UserCredentials): Promise<WriteResult>;
|
setStreamMetadataRaw(stream: string, expectedMetastreamVersion: Long|number, metadata: any, userCredentials?: UserCredentials): Promise<WriteResult>;
|
||||||
getStreamMetadataRaw(stream: string, userCredentials?: UserCredentials): Promise<RawStreamMetadataResult>;
|
getStreamMetadataRaw(stream: string, userCredentials?: UserCredentials): Promise<RawStreamMetadataResult>;
|
||||||
|
|
||||||
on(event: "connected" | "disconnected" | "reconnecting" | "closed" | "error" | "heartbeatInfo", listener: (arg: Error | string | TcpEndPoint | HeartbeatInfo) => void): this;
|
|
||||||
once(event: "connected" | "disconnected" | "reconnecting" | "closed" | "error" | "heartbeatInfo", listener: (arg: Error | string | TcpEndPoint | HeartbeatInfo) => void): this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose helper functions
|
// Expose helper functions
|
||||||
@ -366,6 +380,7 @@ export interface ConnectionSettings {
|
|||||||
// Cluster Settings
|
// Cluster Settings
|
||||||
clusterDns?: string,
|
clusterDns?: string,
|
||||||
maxDiscoverAttempts?: number,
|
maxDiscoverAttempts?: number,
|
||||||
|
discoverDelay?: number,
|
||||||
externalGossipPort?: number,
|
externalGossipPort?: number,
|
||||||
gossipTimeout?: number
|
gossipTimeout?: number
|
||||||
}
|
}
|
||||||
|
10576
package-lock.json
generated
Normal file
10576
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@ -1,20 +1,33 @@
|
|||||||
{
|
{
|
||||||
"name": "node-eventstore-client",
|
"name": "node-eventstore-client",
|
||||||
"version": "0.2.3",
|
"version": "0.2.17",
|
||||||
"description": "A port of the EventStore .Net ClientAPI to Node.js",
|
"description": "A port of the EventStore .Net ClientAPI to Node.js",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0"
|
"node": ">=5.10"
|
||||||
},
|
},
|
||||||
"engineStrict": true,
|
"engineStrict": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm lib/dist.js",
|
"clean": "rm -f lib/dist.js",
|
||||||
"build": "webpack",
|
"prebuild": "npm run clean",
|
||||||
|
"build": "webpack -o ./lib/dist.js",
|
||||||
"pretest": "npm run build",
|
"pretest": "npm run build",
|
||||||
"test": "nodeunit",
|
"test": "nodeunit",
|
||||||
"test-debug": "TESTS_VERBOSE_LOGGING=1 nodeunit",
|
"test-debug": "TESTS_VERBOSE_LOGGING=1 nodeunit",
|
||||||
"prepublish": "npm run build && npm run gendocs",
|
"test:jest:watch": "jest --watch --coverage",
|
||||||
|
"test:single": "npm run compose:single:start && npm run compose:wait && npm run compose:single:test ; npm run compose:single:stop",
|
||||||
|
"test:gossip": "npm run compose:cluster:start && npm run compose:wait && npm run compose:gossip:test ; npm run compose:cluster:stop",
|
||||||
|
"test:cluster": "npm run compose:cluster:start && npm run compose:wait && npm run compose:cluster:test ; npm run compose:cluster:stop",
|
||||||
|
"compose:single:start": "docker-compose -f docker-compose-single.yaml up --build -d",
|
||||||
|
"compose:cluster:start": "docker-compose -f docker-compose-cluster.yaml up --build -d",
|
||||||
|
"compose:single:stop": "docker-compose -f docker-compose-single.yaml down -v --remove-orphans",
|
||||||
|
"compose:cluster:stop": "docker-compose -f docker-compose-cluster.yaml down -v --remove-orphans",
|
||||||
|
"compose:wait": "while [[ \"$(curl -s -o /dev/null -w ''%{http_code}'' localhost:2113/ping)\" != \"200\" ]]; do sleep 5; done",
|
||||||
|
"compose:single:test": "docker-compose -f docker-compose-single.yaml exec nodejs bash -c \"npm i && npm run build && EVENTSTORE_CONNECTION_TYPE=tcp EVENTSTORE_HOST=192.168.33.10 npm run test-debug\"",
|
||||||
|
"compose:cluster:test": "docker-compose -f docker-compose-cluster.yaml exec nodejs bash -c \"npm i && npm run build && EVENTSTORE_CONNECTION_TYPE=gossip EVENTSTORE_HOST=192.168.33.10 EVENTSTORE_HOST_1=192.168.33.10 EVENTSTORE_HOST_2=192.168.33.11 EVENTSTORE_HOST_3=192.168.33.12 npm run test-debug\"",
|
||||||
|
"compose:gossip:test": "docker-compose -f docker-compose-cluster.yaml exec nodejs bash -c \"npm i && npm run build && EVENTSTORE_CONNECTION_TYPE=dns EVENTSTORE_HOST=192.168.33.10 npm run test-debug\"",
|
||||||
|
"prepublishOnly": "npm run build && npm run gendocs",
|
||||||
"gendocs": "rm -rf docs && jsdoc src -r -d docs"
|
"gendocs": "rm -rf docs && jsdoc src -r -d docs"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@ -45,15 +58,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/nicdex/node-eventstore-client#readme",
|
"homepage": "https://github.com/nicdex/node-eventstore-client#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/long": "^3.0.31",
|
"@types/long": "^3.0.32",
|
||||||
"@types/node": "^6.0.47",
|
"@types/node": ">=5.10",
|
||||||
"long": "^3.2",
|
"long": "^3.2.0",
|
||||||
"protobufjs": "^6.7.3",
|
"protobufjs": "^6.8.8",
|
||||||
|
"strict-event-emitter-types": "^1.2.0",
|
||||||
"uuid": "^3.0.1"
|
"uuid": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jsdoc": "^3.5.5",
|
"jest": "^26.4.2",
|
||||||
"nodeunit": "^0.11.1",
|
"jsdoc": "^3.6.3",
|
||||||
"webpack": "^3.3.0"
|
"nodeunit": "^0.11.3",
|
||||||
|
"webpack": "^4.41.2",
|
||||||
|
"webpack-cli": "^3.3.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
62
samples/issue-60.js
Normal file
62
samples/issue-60.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
const client = require('../src/client'); // RWM: Import from npm installed package rather than from src
|
||||||
|
//const client = require("node-eventstore-client");
|
||||||
|
|
||||||
|
const resolveLinkTos = true;
|
||||||
|
|
||||||
|
function resumeEvent(event) {
|
||||||
|
return [
|
||||||
|
event.originalEvent.eventType,
|
||||||
|
[event.originalEventNumber.toNumber(), event.originalStreamId].join('@'),
|
||||||
|
event.originalPosition
|
||||||
|
].join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventAppeared = (subscription, event) => console.log("Event received", resumeEvent(event));
|
||||||
|
|
||||||
|
const subscriptionDropped = (subscription, reason, error) => console.log("Subscription dropped", reason, error);
|
||||||
|
|
||||||
|
const libeProcessingStarted = () => console.log("Live processing started.");
|
||||||
|
|
||||||
|
const credentials = new client.UserCredentials("admin", "changeit");
|
||||||
|
|
||||||
|
const settings = {
|
||||||
|
maxReconnections: 10,
|
||||||
|
reconnectionDelay: 1000, // RWM: slow down the reconnection attempts. 10 seconds to restore connection.
|
||||||
|
};
|
||||||
|
if (process.env.ENABLE_LOGGING) settings.log = console;
|
||||||
|
if (process.env.VERBOSE) settings.verboseLogging = true;
|
||||||
|
const endpoint = "tcp://localhost:1113";
|
||||||
|
const connection = client.createConnection(settings, endpoint);
|
||||||
|
|
||||||
|
connection.connect().catch(err => console.log("Connection failed", err));
|
||||||
|
|
||||||
|
connection.on('heartbeatInfo', heartbeatInfo =>
|
||||||
|
console.log('Heartbeat latency', heartbeatInfo.responseReceivedAt - heartbeatInfo.requestSentAt, 'ms')
|
||||||
|
);
|
||||||
|
|
||||||
|
connection.once("connected", tcpEndPoint => {
|
||||||
|
console.log(`Connected to eventstore at ${tcpEndPoint.host}:${tcpEndPoint.port}`);
|
||||||
|
// RWM: subscribe Stream instead of All
|
||||||
|
connection.subscribeToStreamFrom(
|
||||||
|
"test", // RWM: Stream to subscribe to
|
||||||
|
null,
|
||||||
|
resolveLinkTos,
|
||||||
|
eventAppeared,
|
||||||
|
libeProcessingStarted,
|
||||||
|
subscriptionDropped,
|
||||||
|
credentials
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on("error", error =>
|
||||||
|
console.log(`Error occurred on connection: ${error}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
connection.on("closed", reason =>
|
||||||
|
console.log(`Connection closed, reason: ${reason}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// RWM: Handle the reconnecting event, for better awareness of what's happening
|
||||||
|
connection.on("reconnecting", msg =>
|
||||||
|
console.log(`Reconnecting, msg: ${JSON.stringify(msg, null, 4)}`)
|
||||||
|
)
|
50
samples/subscribe-all-from-events.js
Normal file
50
samples/subscribe-all-from-events.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
const client = require('../src/client');
|
||||||
|
//const client = require("node-eventstore-client");
|
||||||
|
|
||||||
|
const resolveLinkTos = true;
|
||||||
|
|
||||||
|
function resumeEvent(event) {
|
||||||
|
return [
|
||||||
|
event.originalEvent.eventType,
|
||||||
|
[event.originalEventNumber.toNumber(), event.originalStreamId].join('@'),
|
||||||
|
event.originalPosition
|
||||||
|
].join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventAppeared = (subscription, event) => console.log("Event received", resumeEvent(event));
|
||||||
|
|
||||||
|
const subscriptionDropped = (subscription, reason, error) => console.log("Subscription dropped", reason, error);
|
||||||
|
|
||||||
|
const libeProcessingStarted = () => console.log("Live processing started.");
|
||||||
|
|
||||||
|
const credentials = new client.UserCredentials("admin", "changeit");
|
||||||
|
|
||||||
|
const settings = {};
|
||||||
|
const endpoint = "tcp://localhost:1113";
|
||||||
|
const connection = client.createConnection(settings, endpoint);
|
||||||
|
|
||||||
|
connection.connect().catch(err => console.log("Connection failed", err));
|
||||||
|
|
||||||
|
connection.on('heartbeatInfo', heartbeatInfo =>
|
||||||
|
console.log('Heartbeat latency', heartbeatInfo.responseReceivedAt - heartbeatInfo.requestSentAt, 'ms')
|
||||||
|
);
|
||||||
|
|
||||||
|
connection.once("connected", tcpEndPoint => {
|
||||||
|
console.log(`Connected to eventstore at ${tcpEndPoint.host}:${tcpEndPoint.port}`);
|
||||||
|
connection.subscribeToAllFrom(
|
||||||
|
null,
|
||||||
|
resolveLinkTos,
|
||||||
|
eventAppeared,
|
||||||
|
libeProcessingStarted,
|
||||||
|
subscriptionDropped,
|
||||||
|
credentials
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on("error", error =>
|
||||||
|
console.log(`Error occurred on connection: ${error}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
connection.on("closed", reason =>
|
||||||
|
console.log(`Connection closed, reason: ${reason}`)
|
||||||
|
)
|
@ -33,8 +33,8 @@ Object.freeze(streamPosition);
|
|||||||
function createJsonEventData(eventId, event, metadata, type) {
|
function createJsonEventData(eventId, event, metadata, type) {
|
||||||
if (!event || typeof event !== 'object') throw new TypeError("data must be an object.");
|
if (!event || typeof event !== 'object') throw new TypeError("data must be an object.");
|
||||||
|
|
||||||
var eventBuf = new Buffer(JSON.stringify(event));
|
var eventBuf = Buffer.from(JSON.stringify(event));
|
||||||
var metaBuf = metadata ? new Buffer(JSON.stringify(metadata)) : null;
|
var metaBuf = metadata ? Buffer.from(JSON.stringify(metadata)) : null;
|
||||||
return new EventData(eventId, type || event.constructor.name, true, eventBuf, metaBuf);
|
return new EventData(eventId, type || event.constructor.name, true, eventBuf, metaBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,29 +53,31 @@ function createEventData(eventId, type, isJson, data, metadata) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Expose classes
|
// Expose classes
|
||||||
module.exports.Position = results.Position;
|
exports.Position = results.Position;
|
||||||
module.exports.UserCredentials = require('./systemData/userCredentials');
|
exports.UserCredentials = require('./systemData/userCredentials');
|
||||||
module.exports.PersistentSubscriptionSettings = require('./persistentSubscriptionSettings');
|
exports.PersistentSubscriptionSettings = require('./persistentSubscriptionSettings');
|
||||||
module.exports.SystemConsumerStrategies = require('./systemConsumerStrategies');
|
exports.SystemConsumerStrategies = require('./systemConsumerStrategies');
|
||||||
module.exports.GossipSeed = require('./gossipSeed');
|
exports.GossipSeed = require('./gossipSeed');
|
||||||
module.exports.EventStoreConnection = require('./eventStoreConnection');
|
exports.EventStoreConnection = require('./eventStoreConnection');
|
||||||
module.exports.ProjectionsManager = require('./projections/projectionsManager');
|
exports.ProjectionsManager = require('./projections/projectionsManager');
|
||||||
// Expose errors
|
// Expose errors
|
||||||
module.exports.WrongExpectedVersionError = require('./errors/wrongExpectedVersionError');
|
exports.WrongExpectedVersionError = require('./errors/wrongExpectedVersionError');
|
||||||
module.exports.StreamDeletedError = require('./errors/streamDeletedError');
|
exports.StreamDeletedError = require('./errors/streamDeletedError');
|
||||||
module.exports.AccessDeniedError = require('./errors/accessDeniedError');
|
exports.AccessDeniedError = require('./errors/accessDeniedError');
|
||||||
module.exports.ProjectionCommandFailedError = require('./errors/projectionCommandFailedError');
|
exports.ProjectionCommandFailedError = require('./errors/projectionCommandFailedError');
|
||||||
// Expose enums/constants
|
// Expose enums/constants
|
||||||
module.exports.expectedVersion = expectedVersion;
|
exports.expectedVersion = expectedVersion;
|
||||||
module.exports.positions = positions;
|
exports.positions = positions;
|
||||||
module.exports.streamPosition = streamPosition;
|
exports.streamPosition = streamPosition;
|
||||||
module.exports.systemMetadata = require('./common/systemMetadata');
|
exports.systemMetadata = require('./common/systemMetadata');
|
||||||
module.exports.eventReadStatus = results.EventReadStatus;
|
exports.eventReadStatus = results.EventReadStatus;
|
||||||
module.exports.sliceReadStatus = require('./sliceReadStatus');
|
exports.sliceReadStatus = require('./sliceReadStatus');
|
||||||
|
exports.persistentSubscriptionNakEventAction =
|
||||||
|
exports.PersistentSubscriptionNakEventAction = require('./persistentSubscriptionNakEventAction');
|
||||||
// Expose loggers
|
// Expose loggers
|
||||||
module.exports.NoopLogger = require('./common/log/noopLogger');
|
exports.NoopLogger = require('./common/log/noopLogger');
|
||||||
module.exports.FileLogger = require('./common/log/fileLogger');
|
exports.FileLogger = require('./common/log/fileLogger');
|
||||||
// Expose Helper functions
|
// Expose Helper functions
|
||||||
module.exports.createConnection = require('./eventStoreConnection').create;
|
exports.createConnection = require('./eventStoreConnection').create;
|
||||||
module.exports.createJsonEventData = createJsonEventData;
|
exports.createJsonEventData = createJsonEventData;
|
||||||
module.exports.createEventData = createEventData;
|
exports.createEventData = createEventData;
|
||||||
|
@ -44,8 +44,7 @@ AppendToStreamOperation.prototype._inspectResponse = function(response) {
|
|||||||
switch (response.result)
|
switch (response.result)
|
||||||
{
|
{
|
||||||
case ClientMessage.OperationResult.Success:
|
case ClientMessage.OperationResult.Success:
|
||||||
if (this._wasCommitTimeout)
|
if (this._wasCommitTimeout) this.log.debug("IDEMPOTENT WRITE SUCCEEDED FOR %s.", this);
|
||||||
this.log.debug("IDEMPOTENT WRITE SUCCEEDED FOR %s.", this);
|
|
||||||
this._succeed();
|
this._succeed();
|
||||||
return new InspectionResult(InspectionDecision.EndOperation, "Success");
|
return new InspectionResult(InspectionDecision.EndOperation, "Success");
|
||||||
case ClientMessage.OperationResult.PrepareTimeout:
|
case ClientMessage.OperationResult.PrepareTimeout:
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var TcpCommand = require('../systemData/tcpCommand');
|
var TcpCommand = require('../systemData/tcpCommand');
|
||||||
var InspectionDecision = require('../systemData/inspectionDecision');
|
var InspectionDecision = require('../systemData/inspectionDecision');
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var ensure = require('../common/utils/ensure');
|
var ensure = require('../common/utils/ensure');
|
||||||
var OperationBase = require('../clientOperations/operationBase');
|
var OperationBase = require('../clientOperations/operationBase');
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var ensure = require('../common/utils/ensure');
|
|
||||||
var OperationBase = require('../clientOperations/operationBase');
|
var OperationBase = require('../clientOperations/operationBase');
|
||||||
var TcpCommand = require('../systemData/tcpCommand');
|
var TcpCommand = require('../systemData/tcpCommand');
|
||||||
var ClientMessage = require('../messages/clientMessage');
|
var ClientMessage = require('../messages/clientMessage');
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var TcpCommand = require('../systemData/tcpCommand');
|
var TcpCommand = require('../systemData/tcpCommand');
|
||||||
var InspectionDecision = require('../systemData/inspectionDecision');
|
var InspectionDecision = require('../systemData/inspectionDecision');
|
||||||
|
@ -42,11 +42,12 @@ OperationBase.prototype._succeed = function() {
|
|||||||
if (!this._completed) {
|
if (!this._completed) {
|
||||||
this._completed = true;
|
this._completed = true;
|
||||||
|
|
||||||
if (this._response)
|
if (this._response) {
|
||||||
this._cb(null, this._transformResponse(this._response));
|
this._cb(null, this._transformResponse(this._response));
|
||||||
else
|
} else {
|
||||||
this._cb(new Error("No result."))
|
this._cb(new Error("No result."))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
OperationBase.prototype.createNetworkPackage = function(correlationId) {
|
OperationBase.prototype.createNetworkPackage = function(correlationId) {
|
||||||
@ -130,8 +131,7 @@ OperationBase.prototype._inspectNotHandled = function(pkg)
|
|||||||
|
|
||||||
OperationBase.prototype._inspectUnexpectedCommand = function(pkg, expectedCommand)
|
OperationBase.prototype._inspectUnexpectedCommand = function(pkg, expectedCommand)
|
||||||
{
|
{
|
||||||
if (pkg.command === expectedCommand)
|
if (pkg.command === expectedCommand) throw new Error("Command shouldn't be " + TcpCommand.getName(pkg.command));
|
||||||
throw new Error("Command shouldn't be " + TcpCommand.getName(pkg.command));
|
|
||||||
|
|
||||||
this.log.error("Unexpected TcpCommand received.\n"
|
this.log.error("Unexpected TcpCommand received.\n"
|
||||||
+ "Expected: %s, Actual: %s, Flags: %s, CorrelationId: %s\n"
|
+ "Expected: %s, Actual: %s, Flags: %s, CorrelationId: %s\n"
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var TcpCommand = require('../systemData/tcpCommand');
|
var TcpCommand = require('../systemData/tcpCommand');
|
||||||
var ClientMessage = require('../messages/clientMessage');
|
var ClientMessage = require('../messages/clientMessage');
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var TcpCommand = require('../systemData/tcpCommand');
|
var TcpCommand = require('../systemData/tcpCommand');
|
||||||
var ClientMessage = require('../messages/clientMessage');
|
var ClientMessage = require('../messages/clientMessage');
|
||||||
|
@ -59,7 +59,6 @@ ReadEventOperation.prototype._transformResponse = function(response) {
|
|||||||
return new results.EventReadResult(convert(response.result), this._stream, this._eventNumber, response.event);
|
return new results.EventReadResult(convert(response.result), this._stream, this._eventNumber, response.event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function convert(result)
|
function convert(result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var TcpCommand = require('../systemData/tcpCommand');
|
var TcpCommand = require('../systemData/tcpCommand');
|
||||||
var ClientMessage = require('../messages/clientMessage');
|
var ClientMessage = require('../messages/clientMessage');
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var TcpCommand = require('../systemData/tcpCommand');
|
var TcpCommand = require('../systemData/tcpCommand');
|
||||||
var ClientMessage = require('../messages/clientMessage');
|
var ClientMessage = require('../messages/clientMessage');
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var TcpCommand = require('../systemData/tcpCommand');
|
var TcpCommand = require('../systemData/tcpCommand');
|
||||||
var InspectionDecision = require('../systemData/inspectionDecision');
|
var InspectionDecision = require('../systemData/inspectionDecision');
|
||||||
var InspectionResult = require('./../systemData/inspectionResult');
|
var InspectionResult = require('./../systemData/inspectionResult');
|
||||||
var ClientMessage = require('../messages/clientMessage');
|
var ClientMessage = require('../messages/clientMessage');
|
||||||
var EventStoreTransaction = require('../eventStoreTransaction');
|
var EventStoreTransaction = require('../eventStoreTransaction');
|
||||||
var results = require('../results');
|
|
||||||
var AccessDeniedError = require('../errors/accessDeniedError');
|
var AccessDeniedError = require('../errors/accessDeniedError');
|
||||||
var WrongExpectedVersionError = require('../errors/wrongExpectedVersionError');
|
var WrongExpectedVersionError = require('../errors/wrongExpectedVersionError');
|
||||||
var StreamDeletedError = require('../errors/streamDeletedError');
|
var StreamDeletedError = require('../errors/streamDeletedError');
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var TcpCommand = require('../systemData/tcpCommand');
|
var TcpCommand = require('../systemData/tcpCommand');
|
||||||
var TcpFlags = require('../systemData/tcpFlags');
|
var TcpFlags = require('../systemData/tcpFlags');
|
||||||
@ -46,8 +45,7 @@ SubscriptionOperation.prototype._enqueueSend = function(pkg) {
|
|||||||
SubscriptionOperation.prototype.subscribe = function(correlationId, connection) {
|
SubscriptionOperation.prototype.subscribe = function(correlationId, connection) {
|
||||||
if (connection === null) throw new TypeError("connection is null.");
|
if (connection === null) throw new TypeError("connection is null.");
|
||||||
|
|
||||||
if (this._subscription !== null || this._unsubscribed)
|
if (this._subscription !== null || this._unsubscribed) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
this._correlationId = correlationId;
|
this._correlationId = correlationId;
|
||||||
connection.enqueueSend(this._createSubscriptionPackage());
|
connection.enqueueSend(this._createSubscriptionPackage());
|
||||||
@ -76,9 +74,7 @@ SubscriptionOperation.prototype.inspectPackage = function(pkg) {
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = this._inspectPackage(pkg);
|
var result = this._inspectPackage(pkg);
|
||||||
if (result !== null) {
|
if (result !== null) return result;
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (pkg.command)
|
switch (pkg.command)
|
||||||
{
|
{
|
||||||
@ -128,8 +124,9 @@ SubscriptionOperation.prototype.inspectPackage = function(pkg) {
|
|||||||
|
|
||||||
case TcpCommand.NotHandled:
|
case TcpCommand.NotHandled:
|
||||||
{
|
{
|
||||||
if (this._subscription !== null)
|
if (this._subscription !== null) {
|
||||||
throw new Error("NotHandled command appeared while we already subscribed.");
|
throw new Error("NotHandled command appeared while we already subscribed.");
|
||||||
|
}
|
||||||
|
|
||||||
var message = ClientMessage.NotHandled.decode(pkg.data.toBuffer());
|
var message = ClientMessage.NotHandled.decode(pkg.data.toBuffer());
|
||||||
switch (message.reason)
|
switch (message.reason)
|
||||||
@ -172,8 +169,7 @@ SubscriptionOperation.prototype.connectionClosed = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SubscriptionOperation.prototype.timeOutSubscription = function() {
|
SubscriptionOperation.prototype.timeOutSubscription = function() {
|
||||||
if (this._subscription !== null)
|
if (this._subscription !== null) return false;
|
||||||
return false;
|
|
||||||
this.dropSubscription(SubscriptionDropReason.SubscribingError, null);
|
this.dropSubscription(SubscriptionDropReason.SubscribingError, null);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@ -182,9 +178,10 @@ SubscriptionOperation.prototype.dropSubscription = function(reason, err, connect
|
|||||||
if (!this._unsubscribed)
|
if (!this._unsubscribed)
|
||||||
{
|
{
|
||||||
this._unsubscribed = true;
|
this._unsubscribed = true;
|
||||||
if (this._verboseLogging)
|
if (this._verboseLogging) {
|
||||||
this._log.debug("Subscription %s to %s: closing subscription, reason: %s, exception: %s...",
|
this._log.debug("Subscription %s to %s: closing subscription, reason: %s, exception: %s...",
|
||||||
this._correlationId, this._streamId || "<all>", reason, err);
|
this._correlationId, this._streamId || "<all>", reason, err);
|
||||||
|
}
|
||||||
|
|
||||||
if (reason !== SubscriptionDropReason.UserInitiated && this._subscription === null)
|
if (reason !== SubscriptionDropReason.UserInitiated && this._subscription === null)
|
||||||
{
|
{
|
||||||
@ -193,24 +190,31 @@ SubscriptionOperation.prototype.dropSubscription = function(reason, err, connect
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reason === SubscriptionDropReason.UserInitiated && this._subscription !== null && connection !== null)
|
if (reason === SubscriptionDropReason.UserInitiated && this._subscription !== null && connection !== null) {
|
||||||
connection.enqueueSend(this._createUnsubscriptionPackage());
|
connection.enqueueSend(this._createUnsubscriptionPackage());
|
||||||
|
}
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
if (this._subscription !== null)
|
if (this._subscription !== null) {
|
||||||
this._executeAction(function() { self._subscriptionDropped(self._subscription, reason, err); });
|
this._executeAction(function () {
|
||||||
|
self._subscriptionDropped(self._subscription, reason, err);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SubscriptionOperation.prototype._confirmSubscription = function(lastCommitPosition, lastEventNumber) {
|
SubscriptionOperation.prototype._confirmSubscription = function(lastCommitPosition, lastEventNumber) {
|
||||||
if (lastCommitPosition < -1)
|
if (lastCommitPosition < -1) {
|
||||||
throw new Error(util.format("Invalid lastCommitPosition %s on subscription confirmation.", lastCommitPosition));
|
throw new Error(util.format("Invalid lastCommitPosition %s on subscription confirmation.", lastCommitPosition));
|
||||||
if (this._subscription !== null)
|
}
|
||||||
|
if (this._subscription !== null) {
|
||||||
throw new Error("Double confirmation of subscription.");
|
throw new Error("Double confirmation of subscription.");
|
||||||
|
}
|
||||||
|
|
||||||
if (this._verboseLogging)
|
if (this._verboseLogging) {
|
||||||
this._log.debug("Subscription %s to %s: subscribed at CommitPosition: %d, EventNumber: %d.",
|
this._log.debug("Subscription %s to %s: subscribed at CommitPosition: %d, EventNumber: %d.",
|
||||||
this._correlationId, this._streamId || "<all>", lastCommitPosition, lastEventNumber);
|
this._correlationId, this._streamId || "<all>", lastCommitPosition, lastEventNumber);
|
||||||
|
}
|
||||||
|
|
||||||
this._subscription = this._createSubscriptionObject(lastCommitPosition, lastEventNumber);
|
this._subscription = this._createSubscriptionObject(lastCommitPosition, lastEventNumber);
|
||||||
this._cb(null, this._subscription);
|
this._cb(null, this._subscription);
|
||||||
@ -221,15 +225,15 @@ SubscriptionOperation.prototype._createSubscriptionObject = function(lastCommitP
|
|||||||
};
|
};
|
||||||
|
|
||||||
SubscriptionOperation.prototype._onEventAppeared = function(e) {
|
SubscriptionOperation.prototype._onEventAppeared = function(e) {
|
||||||
if (this._unsubscribed)
|
if (this._unsubscribed) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (this._subscription === null) throw new Error("Subscription not confirmed, but event appeared!");
|
if (this._subscription === null) throw new Error("Subscription not confirmed, but event appeared!");
|
||||||
|
|
||||||
if (this._verboseLogging)
|
if (this._verboseLogging) {
|
||||||
this._log.debug("Subscription %s to %s: event appeared (%s, %d, %s @ %s).",
|
this._log.debug("Subscription %s to %s: event appeared (%s, %d, %s @ %s).",
|
||||||
this._correlationId, this._streamId || "<all>",
|
this._correlationId, this._streamId || "<all>",
|
||||||
e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition);
|
e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition);
|
||||||
|
}
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this._executeAction(function() { return self._eventAppeared(self._subscription, e); });
|
this._executeAction(function() { return self._eventAppeared(self._subscription, e); });
|
||||||
|
@ -65,4 +65,3 @@ TransactionalWriteOperation.prototype.toString = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = TransactionalWriteOperation;
|
module.exports = TransactionalWriteOperation;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var ensure = require('../common/utils/ensure');
|
var ensure = require('../common/utils/ensure');
|
||||||
var OperationBase = require('../clientOperations/operationBase');
|
var OperationBase = require('../clientOperations/operationBase');
|
||||||
|
@ -46,7 +46,7 @@ VolatileSubscriptionOperation.prototype._inspectPackage = function(pkg) {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.log(e.stack);
|
this._log.warn(e.stack);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -19,8 +19,7 @@ BufferSegment.prototype.toString = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
BufferSegment.prototype.toBuffer = function() {
|
BufferSegment.prototype.toBuffer = function() {
|
||||||
if (this.offset === 0 && this.count === this.buffer.length)
|
if (this.offset === 0 && this.count === this.buffer.length) return this.buffer;
|
||||||
return this.buffer;
|
|
||||||
return this.buffer.slice(this.offset, this.offset + this.count);
|
return this.buffer.slice(this.offset, this.offset + this.count);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,14 +14,14 @@ function parse(s, buf, offset) {
|
|||||||
var ii = 0;
|
var ii = 0;
|
||||||
|
|
||||||
if (buf) buf.fill(0, i, i + 16);
|
if (buf) buf.fill(0, i, i + 16);
|
||||||
buf = buf || new Buffer(16);
|
buf = buf || Buffer.alloc(16);
|
||||||
s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
|
s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
|
||||||
if (ii < 16) { // Don't overflow!
|
if (ii < 16) { // Don't overflow!
|
||||||
buf[i + ii++] = _hexToByte[oct];
|
buf[i + ii++] = _hexToByte[oct];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var buf2 = new Buffer(buf.slice(i, i + 16));
|
var buf2 = Buffer.from(buf.slice(i, i + 16));
|
||||||
buf[i + 0] = buf2[3];
|
buf[i + 0] = buf2[3];
|
||||||
buf[i + 1] = buf2[2];
|
buf[i + 1] = buf2[2];
|
||||||
buf[i + 2] = buf2[1];
|
buf[i + 2] = buf2[1];
|
||||||
|
@ -26,6 +26,11 @@ FileLogger.prototype.info = function() {
|
|||||||
fs.appendFileSync(this._filePath, line);
|
fs.appendFileSync(this._filePath, line);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FileLogger.prototype.warn = function() {
|
||||||
|
var line = createLine('WARN', arguments, 0);
|
||||||
|
fs.appendFileSync(this._filePath, line);
|
||||||
|
};
|
||||||
|
|
||||||
FileLogger.prototype.error = function(e) {
|
FileLogger.prototype.error = function(e) {
|
||||||
var hasError = e instanceof Error;
|
var hasError = e instanceof Error;
|
||||||
var line = createLine('ERROR', arguments, hasError ? 1 : 0);
|
var line = createLine('ERROR', arguments, hasError ? 1 : 0);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
function NoopLogger() {
|
function NoopLogger() {
|
||||||
}
|
}
|
||||||
NoopLogger.prototype.error = function() {};
|
|
||||||
NoopLogger.prototype.debug = function() {};
|
NoopLogger.prototype.debug = function() {};
|
||||||
NoopLogger.prototype.info = function() {};
|
NoopLogger.prototype.info = function() {};
|
||||||
|
NoopLogger.prototype.warn = function() {};
|
||||||
|
NoopLogger.prototype.error = function() {};
|
||||||
|
|
||||||
module.exports = NoopLogger;
|
module.exports = NoopLogger;
|
@ -1,5 +1,5 @@
|
|||||||
const SystemEventTypes = {
|
const SystemEventTypes = Object.freeze({
|
||||||
StreamMetadata: '$metadata'
|
StreamMetadata: '$metadata'
|
||||||
};
|
});
|
||||||
|
|
||||||
module.exports = SystemEventTypes;
|
module.exports = SystemEventTypes;
|
@ -1,4 +1,4 @@
|
|||||||
const SystemMetadata = {
|
const SystemMetadata = Object.freeze({
|
||||||
maxAge: '$maxAge',
|
maxAge: '$maxAge',
|
||||||
maxCount: '$maxCount',
|
maxCount: '$maxCount',
|
||||||
truncateBefore: '$tb',
|
truncateBefore: '$tb',
|
||||||
@ -11,7 +11,6 @@ const SystemMetadata = {
|
|||||||
aclMetaWrite: '$mw',
|
aclMetaWrite: '$mw',
|
||||||
userStreamAcl: '$userStreamAcl',
|
userStreamAcl: '$userStreamAcl',
|
||||||
systemStreamAcl: '$systemStreamAcl'
|
systemStreamAcl: '$systemStreamAcl'
|
||||||
};
|
});
|
||||||
Object.freeze(SystemMetadata);
|
|
||||||
|
|
||||||
module.exports = SystemMetadata;
|
module.exports = SystemMetadata;
|
@ -1,6 +1,6 @@
|
|||||||
module.exports.metastreamOf = function(stream) {
|
exports.metastreamOf = function(stream) {
|
||||||
return '$$' + stream;
|
return '$$' + stream;
|
||||||
};
|
};
|
||||||
module.exports.isMetastream = function(stream) {
|
exports.isMetastream = function(stream) {
|
||||||
return stream.indexOf('$$') === 0;
|
return stream.indexOf('$$') === 0;
|
||||||
};
|
};
|
@ -1,20 +1,16 @@
|
|||||||
var Long = require('long');
|
var Long = require('long');
|
||||||
|
|
||||||
module.exports.notNullOrEmpty = function(value, name) {
|
module.exports.notNullOrEmpty = function(value, name) {
|
||||||
if (value === null)
|
if (value === null) throw new TypeError(name + " should not be null.");
|
||||||
throw new TypeError(name + " should not be null.");
|
if (value === '') throw new Error(name + " should not be empty.");
|
||||||
if (value === '')
|
|
||||||
throw new Error(name + " should not be empty.");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.notNull = function(value, name) {
|
module.exports.notNull = function(value, name) {
|
||||||
if (value === null)
|
if (value === null) throw new TypeError(name + " should not be null.");
|
||||||
throw new TypeError(name + " should not be null.");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function isInteger(value, name) {
|
function isInteger(value, name) {
|
||||||
if (typeof value !== 'number' || value % 1 !== 0)
|
if (typeof value !== 'number' || value % 1 !== 0) throw new TypeError(name + " should be an integer.");
|
||||||
throw new TypeError(name + " should be an integer.");
|
|
||||||
}
|
}
|
||||||
module.exports.isInteger = isInteger;
|
module.exports.isInteger = isInteger;
|
||||||
|
|
||||||
@ -27,24 +23,23 @@ module.exports.isLongOrInteger = function(value, name) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports.isArrayOf = function(expectedType, value, name) {
|
module.exports.isArrayOf = function(expectedType, value, name) {
|
||||||
if (!Array.isArray(value))
|
if (!Array.isArray(value)) throw new TypeError(name + " should be an array.");
|
||||||
throw new TypeError(name + " should be an array.");
|
if (!value.every(function(x) { return x instanceof expectedType; })) {
|
||||||
if (!value.every(function(x) { return x instanceof expectedType; }))
|
|
||||||
throw new TypeError([name, " should be an array of ", expectedType.name, "."].join(""));
|
throw new TypeError([name, " should be an array of ", expectedType.name, "."].join(""));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.isTypeOf = function(expectedType, value, name, nullAllowed) {
|
module.exports.isTypeOf = function(expectedType, value, name, nullAllowed) {
|
||||||
if (nullAllowed && value === null) return;
|
if (nullAllowed && value === null) return;
|
||||||
if (!(value instanceof expectedType))
|
if (!(value instanceof expectedType)) {
|
||||||
throw new TypeError([name, " should be of type '", expectedType.name, "'", nullAllowed ? " or null": "", "."].join(""));
|
throw new TypeError([name, " should be of type '", expectedType.name, "'", nullAllowed ? " or null" : "", "."].join(""));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.positive = function(value, name) {
|
module.exports.positive = function(value, name) {
|
||||||
if (value <= 0)
|
if (value <= 0) throw new Error(name + " should be positive.");
|
||||||
throw new Error(name + " should be positive.");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.nonNegative = function(value, name) {
|
module.exports.nonNegative = function(value, name) {
|
||||||
if (value < 0)
|
if (value < 0) throw new Error(name + " should be non-negative.");
|
||||||
throw new Error(name + " should be non-negative.");
|
|
||||||
};
|
};
|
28
src/common/utils/shuffle.js
Normal file
28
src/common/utils/shuffle.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
function rndNext(min, max) {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shuffle (arr, from, to) {
|
||||||
|
if (arr.length <= 1){
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
if (!from) {
|
||||||
|
from = 0;
|
||||||
|
}
|
||||||
|
if (!to) {
|
||||||
|
to = arr.length - 1;
|
||||||
|
}
|
||||||
|
const newArr = [...arr];
|
||||||
|
if (from >= to) return;
|
||||||
|
for (var current = from; current <= to; ++current) {
|
||||||
|
var index = rndNext(current, to + 1);
|
||||||
|
var tmp = newArr[index];
|
||||||
|
newArr[index] = newArr[current];
|
||||||
|
newArr[current] = tmp;
|
||||||
|
}
|
||||||
|
return newArr;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = shuffle;
|
163
src/core/cluster/clusterDiscoverer.js
Normal file
163
src/core/cluster/clusterDiscoverer.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
const ClusterInfo = require('./clusterInfo');
|
||||||
|
const GossipSeed = require('../../gossipSeed');
|
||||||
|
const NodeEndPoints = require('./nodeEndpoints');
|
||||||
|
const shuffle = require('../../common/utils/shuffle');
|
||||||
|
|
||||||
|
function wait(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClusterDiscoverer
|
||||||
|
* @constructor
|
||||||
|
* @class
|
||||||
|
* @param {Logger} log - Logger instance
|
||||||
|
* @param {Object} settings - Settings object
|
||||||
|
* @param {Object} dnsService - DNS service to perform DNS lookup
|
||||||
|
* @param {Object} httpService - HTTP service to perform http requests
|
||||||
|
*/
|
||||||
|
function ClusterDiscoverer(log, settings, dnsService, httpService) {
|
||||||
|
if (!settings.clusterDns && (!settings.seeds || settings.seeds.length === 0))
|
||||||
|
throw new Error('Both clusterDns and seeds are null/empty.');
|
||||||
|
this._log = log;
|
||||||
|
|
||||||
|
this._settings = settings;
|
||||||
|
this._dnsService = dnsService;
|
||||||
|
this._httpService = httpService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover Cluster endpoints
|
||||||
|
* @param {Object} failedTcpEndPoint - The failed TCP endpoint which were used by the handler
|
||||||
|
* @returns {Promise.<NodeEndPoints>}
|
||||||
|
*/
|
||||||
|
ClusterDiscoverer.prototype.discover = async function (failedTcpEndPoint) {
|
||||||
|
let attempts = 0;
|
||||||
|
while (attempts++ < this._settings.maxDiscoverAttempts) {
|
||||||
|
try {
|
||||||
|
const candidates = await this._getGossipCandidates(this._settings.managerExternalHttpPort);
|
||||||
|
const gossipSeeds = candidates.filter(
|
||||||
|
(candidate) =>
|
||||||
|
!failedTcpEndPoint ||
|
||||||
|
!(candidate.endPoint.host === failedTcpEndPoint.host && candidate.endPoint.port === failedTcpEndPoint.port)
|
||||||
|
);
|
||||||
|
let gossipSeedsIndex = 0;
|
||||||
|
let clusterInfo;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
clusterInfo = await this._clusterInfo(gossipSeeds[gossipSeedsIndex], this._settings.gossipTimeout);
|
||||||
|
if (!clusterInfo.bestNode) {
|
||||||
|
this._log.info(
|
||||||
|
`Discovering attempt ${attempts}/${this._settings.maxDiscoverAttempts} failed: no candidate found.`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
} while (++gossipSeedsIndex < gossipSeeds.length);
|
||||||
|
if (clusterInfo) {
|
||||||
|
return NodeEndPoints.createFromGossipMember(clusterInfo.bestNode);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this._log.info(
|
||||||
|
`Discovering attempt ${attempts}/${this._settings.maxDiscoverAttempts} failed with error: ${err}.\n${err.stack}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await wait(this._settings.discoverDelay);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to discover candidate in ${this._settings.maxDiscoverAttempts} attempts.`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get gossip candidates either from DNS or from gossipSeeds settings
|
||||||
|
* @private
|
||||||
|
* @param {Number} managerExternalHttpPort - Http port of the manager (or the http port of the node for OSS clusters)
|
||||||
|
* @returns {Promise.<GossipSeed[]>}
|
||||||
|
*/
|
||||||
|
ClusterDiscoverer.prototype._getGossipCandidates = async function (managerExternalHttpPort) {
|
||||||
|
const gossipSeeds =
|
||||||
|
this._settings.seeds && this._settings.seeds.length > 0
|
||||||
|
? this._settings.seeds
|
||||||
|
: (await this._resolveDns(this._settings.clusterDns)).map(
|
||||||
|
(address) => new GossipSeed({ host: address, port: managerExternalHttpPort }, undefined)
|
||||||
|
);
|
||||||
|
return shuffle(gossipSeeds);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the cluster DNS discovery address to retrieve belonging ip addresses
|
||||||
|
* @private
|
||||||
|
* @param {String} clusterDns - Cluster DNS discovery address
|
||||||
|
* @returns {Promise.<String[]>}
|
||||||
|
*/
|
||||||
|
ClusterDiscoverer.prototype._resolveDns = async function (clusterDns) {
|
||||||
|
const dnsOptions = {
|
||||||
|
family: 4,
|
||||||
|
hints: this._dnsService.ADDRCONFIG | this._dnsService.V4MAPPED,
|
||||||
|
all: true,
|
||||||
|
};
|
||||||
|
const result = await this._dnsService.lookup(clusterDns, dnsOptions);
|
||||||
|
if (!result || result.length === 0) {
|
||||||
|
throw new Error(`No result from dns lookup for ${clusterDns}`);
|
||||||
|
}
|
||||||
|
return result.map((address) => address.address);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cluster informations (gossip members)
|
||||||
|
* @param {GossipSeed} candidate - candidate to get informations from
|
||||||
|
* @param {Number} timeout - timeout for the http request
|
||||||
|
* @returns {Promise.<ClusterInfo>}
|
||||||
|
*/
|
||||||
|
ClusterDiscoverer.prototype._clusterInfo = async function (candidate, timeout) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const options = {
|
||||||
|
host: candidate.endPoint.host,
|
||||||
|
port: candidate.endPoint.port,
|
||||||
|
path: '/gossip?format=json',
|
||||||
|
timeout: timeout,
|
||||||
|
};
|
||||||
|
if (candidate.hostHeader) {
|
||||||
|
options.headers = {
|
||||||
|
Host: candidate.hostHeader,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = this._httpService.request(options, (res) => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
this._log.info('Trying to get gossip from', candidate, 'failed with status code:', res.statusCode);
|
||||||
|
reject(new Error(`Gossip candidate returns a ${res.statusCode} error`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let result = '';
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
result += chunk.toString();
|
||||||
|
});
|
||||||
|
res.on('end', function () {
|
||||||
|
try {
|
||||||
|
result = JSON.parse(result);
|
||||||
|
} catch (e) {
|
||||||
|
reject(new Error('Unable to parse gossip response'));
|
||||||
|
}
|
||||||
|
resolve(new ClusterInfo(result.members));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.setTimeout(timeout);
|
||||||
|
|
||||||
|
request.on('timeout', () => {
|
||||||
|
this._log.info('Trying to get gossip from', candidate, 'timed out.');
|
||||||
|
request.destroy();
|
||||||
|
reject(new Error('Connection to gossip timed out'));
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('error', (error) => {
|
||||||
|
this._log.info('Trying to get gossip from', candidate, 'errored', error);
|
||||||
|
request.destroy();
|
||||||
|
reject(new Error('Connection to gossip errored'));
|
||||||
|
});
|
||||||
|
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ClusterDiscoverer;
|
36
src/core/cluster/clusterInfo.js
Normal file
36
src/core/cluster/clusterInfo.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
const MemberInfo = require('./memberInfo.js');
|
||||||
|
|
||||||
|
const VNodeStates = Object.freeze({
|
||||||
|
Initializing: 0,
|
||||||
|
Unknown: 1,
|
||||||
|
PreReplica: 2,
|
||||||
|
CatchingUp: 3,
|
||||||
|
Clone: 4,
|
||||||
|
Slave: 5,
|
||||||
|
PreMaster: 6,
|
||||||
|
Master: 7,
|
||||||
|
Manager: 8,
|
||||||
|
ShuttingDown: 9,
|
||||||
|
Shutdown: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
function ClusterInfo(members) {
|
||||||
|
this._members = members.map(member => new MemberInfo(member));
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'bestNode', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return this._getBestNode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ClusterInfo.prototype._getBestNode = function () {
|
||||||
|
return this._members
|
||||||
|
.filter(member => member.isAlive && member.isAllowedToConnect)
|
||||||
|
.sort(function (a, b) {
|
||||||
|
return VNodeStates[b.state] - VNodeStates[a.state];
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ClusterInfo;
|
73
src/core/cluster/memberInfo.js
Normal file
73
src/core/cluster/memberInfo.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
const NOT_ALLOWED_STATES = [
|
||||||
|
'Manager',
|
||||||
|
'ShuttingDown',
|
||||||
|
'Shutdown'
|
||||||
|
];
|
||||||
|
|
||||||
|
function MemberInfo(informations) {
|
||||||
|
this._instanceId = informations.instanceId;
|
||||||
|
this._timeStamp = informations.timeStamp;
|
||||||
|
this._state = informations.state;
|
||||||
|
this._isAlive = informations.isAlive;
|
||||||
|
this._internalTcpIp = informations.internalTcpIp;
|
||||||
|
this._internalTcpPort = informations.internalTcpPort;
|
||||||
|
this._internalSecureTcpPort = informations.internalSecureTcpPort;
|
||||||
|
this._externalTcpIp = informations.externalTcpIp;
|
||||||
|
this._externalTcpPort = informations.externalTcpPort;
|
||||||
|
this._externalSecureTcpPort = informations.externalSecureTcpPort;
|
||||||
|
this._internalHttpIp = informations.internalHttpIp;
|
||||||
|
this._internalHttpPort = informations.internalHttpPort;
|
||||||
|
this._externalHttpIp = informations.externalHttpIp;
|
||||||
|
this._externalHttpPort = informations.externalHttpPort;
|
||||||
|
this._lastCommitPosition = informations.lastCommitPosition;
|
||||||
|
this._writerCheckpoint = informations.writerCheckpoint;
|
||||||
|
this._chaserCheckpoint = informations.chaserCheckpoint;
|
||||||
|
this._epochPosition = informations.epochPosition;
|
||||||
|
this._epochNumber = informations.epochNumber;
|
||||||
|
this._epochId = informations.epochId;
|
||||||
|
this._nodePriority = informations.nodePriority;
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'state', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'isAllowedToConnect', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return !NOT_ALLOWED_STATES.includes(this._state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'isAlive', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return this._isAlive;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'externalTcpIp', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return this._externalTcpIp;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'externalTcpPort', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return this._externalTcpPort;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'externalSecureTcpPort', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function () {
|
||||||
|
return this._externalSecureTcpPort;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MemberInfo;
|
23
src/core/cluster/nodeEndpoints.js
Normal file
23
src/core/cluster/nodeEndpoints.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
function NodeEndPoints(tcpEndPoint, secureTcpEndPoint) {
|
||||||
|
if (tcpEndPoint === null && secureTcpEndPoint === null) throw new Error('Both endpoints are null.');
|
||||||
|
Object.defineProperties(this, {
|
||||||
|
tcpEndPoint: {
|
||||||
|
enumerable: true,
|
||||||
|
value: tcpEndPoint
|
||||||
|
},
|
||||||
|
secureTcpEndPoint: {
|
||||||
|
enumerable: true,
|
||||||
|
value: secureTcpEndPoint
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeEndPoints.createFromGossipMember = function (member) {
|
||||||
|
const normTcp = { host: member.externalTcpIp, port: member.externalTcpPort };
|
||||||
|
const secTcp = member.externalSecureTcpPort > 0
|
||||||
|
? { host: member.externalTcpIp, port: member.externalSecureTcpPort }
|
||||||
|
: null;
|
||||||
|
return new NodeEndPoints(normTcp, secTcp);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = NodeEndPoints
|
@ -1,254 +0,0 @@
|
|||||||
var http = require('http');
|
|
||||||
var util = require('util');
|
|
||||||
var dns = require('dns');
|
|
||||||
var GossipSeed = require('../gossipSeed');
|
|
||||||
|
|
||||||
function NodeEndPoints(tcpEndPoint, secureTcpEndPoint) {
|
|
||||||
if (tcpEndPoint === null && secureTcpEndPoint === null) throw new Error('Both endpoints are null.');
|
|
||||||
Object.defineProperties(this, {
|
|
||||||
tcpEndPoint: {
|
|
||||||
enumerable: true,
|
|
||||||
value: tcpEndPoint
|
|
||||||
},
|
|
||||||
secureTcpEndPoint: {
|
|
||||||
enumerable: true,
|
|
||||||
value: secureTcpEndPoint
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function ClusterDnsEndPointDiscoverer(log, clusterDns, maxDiscoverAttempts, managerExternalHttpPort, gossipSeeds, gossipTimeout) {
|
|
||||||
if (!clusterDns && (!gossipSeeds || gossipSeeds.length === 0)) throw new Error('Both clusterDns and gossipSeeds are null/empty.');
|
|
||||||
this._log = log;
|
|
||||||
this._clusterDns = clusterDns;
|
|
||||||
this._maxDiscoverAttempts = maxDiscoverAttempts;
|
|
||||||
this._managerExternalHttpPort = managerExternalHttpPort;
|
|
||||||
this._gossipSeeds = gossipSeeds;
|
|
||||||
this._gossipTimeout = gossipTimeout;
|
|
||||||
this._oldGossip = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClusterDnsEndPointDiscoverer.prototype.discover = function(failedTcpEndPoint) {
|
|
||||||
var attempt = 1;
|
|
||||||
var self = this;
|
|
||||||
function discover(resolve, reject) {
|
|
||||||
self._discoverEndPoint(failedTcpEndPoint)
|
|
||||||
.then(function (endPoints) {
|
|
||||||
if (!endPoints)
|
|
||||||
self._log.info(util.format("Discovering attempt %d/%d failed: no candidate found.", attempt, self._maxDiscoverAttempts));
|
|
||||||
return endPoints;
|
|
||||||
})
|
|
||||||
.catch(function (exc) {
|
|
||||||
self._log.info(util.format("Discovering attempt %d/%d failed with error: %s.\n%s", attempt, self._maxDiscoverAttempts, exc, exc.stack));
|
|
||||||
})
|
|
||||||
.then(function (endPoints) {
|
|
||||||
if (endPoints)
|
|
||||||
return resolve(endPoints);
|
|
||||||
if (attempt++ === self._maxDiscoverAttempts)
|
|
||||||
return reject(new Error('Failed to discover candidate in ' + self._maxDiscoverAttempts + ' attempts.'));
|
|
||||||
setTimeout(discover, 500, resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
discover(resolve, reject);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Discover Cluster endpoints
|
|
||||||
* @param {Object} failedTcpEndPoint
|
|
||||||
* @returns {Promise.<NodeEndPoints>}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ClusterDnsEndPointDiscoverer.prototype._discoverEndPoint = function (failedTcpEndPoint) {
|
|
||||||
try {
|
|
||||||
var mainPromise = this._oldGossip
|
|
||||||
? Promise.resolve(this._getGossipCandidatesFromOldGossip(this._oldGossip, failedTcpEndPoint))
|
|
||||||
: this._getGossipCandidatesFromDns();
|
|
||||||
var self = this;
|
|
||||||
var j = 0;
|
|
||||||
return mainPromise.then(function (gossipCandidates) {
|
|
||||||
var loopPromise = Promise.resolve();
|
|
||||||
for (var i = 0; i < gossipCandidates.length; i++) {
|
|
||||||
loopPromise = loopPromise.then(function (endPoints) {
|
|
||||||
if (endPoints) return endPoints;
|
|
||||||
return self._tryGetGossipFrom(gossipCandidates[j++])
|
|
||||||
.then(function (gossip) {
|
|
||||||
if (!gossip || !gossip.members || gossip.members.length === 0)
|
|
||||||
return;
|
|
||||||
var bestNode = self._tryDetermineBestNode(gossip.members);
|
|
||||||
if (bestNode) {
|
|
||||||
self._oldGossip = gossip.members;
|
|
||||||
return bestNode;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return loopPromise;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ClusterDnsEndPointDiscoverer.prototype._getGossipCandidatesFromOldGossip = function (oldGossip, failedTcpEndPoint) {
|
|
||||||
if (!failedTcpEndPoint) return this._arrangeGossipCandidates(oldGossip);
|
|
||||||
var gossipCandidates = oldGossip.filter(function(x) {
|
|
||||||
return !(x.externalTcpPort === failedTcpEndPoint.port && x.externalTcpIp === failedTcpEndPoint.host);
|
|
||||||
});
|
|
||||||
return this._arrangeGossipCandidates(gossipCandidates);
|
|
||||||
};
|
|
||||||
|
|
||||||
ClusterDnsEndPointDiscoverer.prototype._arrangeGossipCandidates = function (members) {
|
|
||||||
var result = new Array(members.length);
|
|
||||||
var i = -1;
|
|
||||||
var j = members.length;
|
|
||||||
for (var k = 0; k < members.length; ++k)
|
|
||||||
{
|
|
||||||
if (members[k].state === 'Manager')
|
|
||||||
result[--j] = new GossipSeed({host: members[k].externalHttpIp, port: members[k].externalHttpPort});
|
|
||||||
else
|
|
||||||
result[++i] = new GossipSeed({host: members[k].externalHttpIp, port: members[k].externalHttpPort});
|
|
||||||
}
|
|
||||||
this._randomShuffle(result, 0, i); // shuffle nodes
|
|
||||||
this._randomShuffle(result, j, members.length - 1); // shuffle managers
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
ClusterDnsEndPointDiscoverer.prototype._getGossipCandidatesFromDns = function () {
|
|
||||||
var self = this;
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
if (self._gossipSeeds && self._gossipSeeds.length > 0) {
|
|
||||||
var endpoints = self._gossipSeeds;
|
|
||||||
self._randomShuffle(endpoints, 0, endpoints.length - 1);
|
|
||||||
resolve(endpoints);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const dnsOptions = {
|
|
||||||
family: 4,
|
|
||||||
hints: dns.ADDRCONFIG | dns.V4MAPPED,
|
|
||||||
all: true
|
|
||||||
};
|
|
||||||
dns.lookup(self._clusterDns, dnsOptions, function (err, addresses) {
|
|
||||||
if (err) {
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
if (!addresses || addresses.length === 0) {
|
|
||||||
return reject(new Error('No result from dns lookup for ' + self._clusterDns));
|
|
||||||
}
|
|
||||||
var endpoints = addresses.map(function (x) {
|
|
||||||
return new GossipSeed({host: x.address, port: self._managerExternalHttpPort});
|
|
||||||
});
|
|
||||||
resolve(endpoints);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
ClusterDnsEndPointDiscoverer.prototype._tryGetGossipFrom = function (endPoint) {
|
|
||||||
var options = {
|
|
||||||
host: endPoint.endPoint.host,
|
|
||||||
port: endPoint.endPoint.port,
|
|
||||||
path: '/gossip?format=json'
|
|
||||||
};
|
|
||||||
if (endPoint.hostHeader) {
|
|
||||||
options.headers = {'Host': endPoint.hostHeader};
|
|
||||||
}
|
|
||||||
this._log.info('Try get gossip from', endPoint);
|
|
||||||
var self = this;
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
var timedout = false;
|
|
||||||
http.request(options, function (res) {
|
|
||||||
if (timedout) return;
|
|
||||||
var result = '';
|
|
||||||
if (res.statusCode !== 200) {
|
|
||||||
self._log.info('Trying to get gossip from', endPoint, 'failed with status code:', res.statusCode);
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.on('data', function (chunk) {
|
|
||||||
result += chunk.toString();
|
|
||||||
});
|
|
||||||
res.on('end', function () {
|
|
||||||
try {
|
|
||||||
result = JSON.parse(result);
|
|
||||||
} catch (e) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
resolve(result);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.setTimeout(self._gossipTimeout, function () {
|
|
||||||
self._log.info('Trying to get gossip from', endPoint, 'timed out.');
|
|
||||||
timedout = true;
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.on('error', function (e) {
|
|
||||||
if (timedout) return;
|
|
||||||
self._log.info('Trying to get gossip from', endPoint, 'failed with error:', e);
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.end();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const VNodeStates = {
|
|
||||||
'Initializing': 0,
|
|
||||||
'Unknown': 1,
|
|
||||||
'PreReplica': 2,
|
|
||||||
'CatchingUp': 3,
|
|
||||||
'Clone': 4,
|
|
||||||
'Slave': 5,
|
|
||||||
'PreMaster': 6,
|
|
||||||
'Master': 7,
|
|
||||||
'Manager': 8,
|
|
||||||
'ShuttingDown': 9,
|
|
||||||
'Shutdown': 10
|
|
||||||
};
|
|
||||||
|
|
||||||
ClusterDnsEndPointDiscoverer.prototype._tryDetermineBestNode = function (members) {
|
|
||||||
var notAllowedStates = [
|
|
||||||
'Manager',
|
|
||||||
'ShuttingDown',
|
|
||||||
'Shutdown'
|
|
||||||
];
|
|
||||||
var node = members
|
|
||||||
.filter(function (x) {
|
|
||||||
return (x.isAlive && notAllowedStates.indexOf(x.state) === -1);
|
|
||||||
})
|
|
||||||
.sort(function (a, b) {
|
|
||||||
return VNodeStates[b.state] - VNodeStates[a.state];
|
|
||||||
})[0];
|
|
||||||
if (!node)
|
|
||||||
{
|
|
||||||
//_log.Info("Unable to locate suitable node. Gossip info:\n{0}.", string.Join("\n", members.Select(x => x.ToString())));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var normTcp = {host: node.externalTcpIp, port: node.externalTcpPort};
|
|
||||||
var secTcp = node.externalSecureTcpPort > 0
|
|
||||||
? {host: externalTcpIp, port: node.externalSecureTcpPort}
|
|
||||||
: null;
|
|
||||||
this._log.info(util.format("Discovering: found best choice [%j,%j] (%s).", normTcp, secTcp === null ? "n/a" : secTcp, node.state));
|
|
||||||
return new NodeEndPoints(normTcp, secTcp);
|
|
||||||
};
|
|
||||||
|
|
||||||
function rndNext(min, max) {
|
|
||||||
min = Math.ceil(min);
|
|
||||||
max = Math.floor(max);
|
|
||||||
return Math.floor(Math.random() * (max - min)) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClusterDnsEndPointDiscoverer.prototype._randomShuffle = function (arr, i, j) {
|
|
||||||
if (i >= j)
|
|
||||||
return;
|
|
||||||
for (var k = i; k <= j; ++k)
|
|
||||||
{
|
|
||||||
var index = rndNext(k, j + 1);
|
|
||||||
var tmp = arr[index];
|
|
||||||
arr[index] = arr[k];
|
|
||||||
arr[k] = tmp;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = ClusterDnsEndPointDiscoverer;
|
|
@ -17,14 +17,14 @@ var TcpCommand = require('../systemData/tcpCommand');
|
|||||||
var TcpFlags = require('../systemData/tcpFlags');
|
var TcpFlags = require('../systemData/tcpFlags');
|
||||||
var InspectionDecision = require('../systemData/inspectionDecision');
|
var InspectionDecision = require('../systemData/inspectionDecision');
|
||||||
|
|
||||||
const ConnectionState = {
|
const ConnectionState = Object.freeze({
|
||||||
Init: 'init',
|
Init: 'init',
|
||||||
Connecting: 'connecting',
|
Connecting: 'connecting',
|
||||||
Connected: 'connected',
|
Connected: 'connected',
|
||||||
Closed: 'closed'
|
Closed: 'closed'
|
||||||
};
|
});
|
||||||
|
|
||||||
const ConnectingPhase = {
|
const ConnectingPhase = Object.freeze({
|
||||||
Invalid: 'invalid',
|
Invalid: 'invalid',
|
||||||
Reconnecting: 'reconnecting',
|
Reconnecting: 'reconnecting',
|
||||||
EndPointDiscovery: 'endpointDiscovery',
|
EndPointDiscovery: 'endpointDiscovery',
|
||||||
@ -32,7 +32,7 @@ const ConnectingPhase = {
|
|||||||
Authentication: 'authentication',
|
Authentication: 'authentication',
|
||||||
Identification: 'identification',
|
Identification: 'identification',
|
||||||
Connected: 'connected'
|
Connected: 'connected'
|
||||||
};
|
});
|
||||||
|
|
||||||
const TimerPeriod = 200;
|
const TimerPeriod = 200;
|
||||||
const TimerTickMessage = new messages.TimerTickMessage();
|
const TimerTickMessage = new messages.TimerTickMessage();
|
||||||
@ -50,7 +50,7 @@ function EventStoreConnectionLogicHandler(esConnection, settings) {
|
|||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
this._esConnection = esConnection;
|
this._esConnection = esConnection;
|
||||||
this._settings = settings;
|
this._settings = settings;
|
||||||
this._queue = new SimpleQueuedHandler();
|
this._queue = new SimpleQueuedHandler(this._settings.log);
|
||||||
this._state = ConnectionState.Init;
|
this._state = ConnectionState.Init;
|
||||||
this._connectingPhase = ConnectingPhase.Invalid;
|
this._connectingPhase = ConnectingPhase.Invalid;
|
||||||
this._endpointDiscoverer = null;
|
this._endpointDiscoverer = null;
|
||||||
@ -187,8 +187,7 @@ EventStoreConnectionLogicHandler.prototype._closeConnection = function(reason, e
|
|||||||
|
|
||||||
this._logInfo("Closed. Reason: %s", reason);
|
this._logInfo("Closed. Reason: %s", reason);
|
||||||
|
|
||||||
if (error)
|
if (error) this.emit('error', error);
|
||||||
this.emit('error', error);
|
|
||||||
|
|
||||||
this.emit('closed', reason);
|
this.emit('closed', reason);
|
||||||
};
|
};
|
||||||
@ -205,7 +204,7 @@ EventStoreConnectionLogicHandler.prototype._closeTcpConnection = function(reason
|
|||||||
this._connection = null;
|
this._connection = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
var _nextSeqNo = -1;
|
var _nextSeqNo = 0;
|
||||||
function createOperationItem(operation, maxRetries, timeout) {
|
function createOperationItem(operation, maxRetries, timeout) {
|
||||||
var operationItem = {
|
var operationItem = {
|
||||||
seqNo: _nextSeqNo++,
|
seqNo: _nextSeqNo++,
|
||||||
@ -283,10 +282,11 @@ EventStoreConnectionLogicHandler.prototype._startSubscription = function(msg) {
|
|||||||
this._state === ConnectionState.Connected ? "fire" : "enqueue",
|
this._state === ConnectionState.Connected ? "fire" : "enqueue",
|
||||||
operation.constructor.name, operation, msg.maxRetries, msg.timeout);
|
operation.constructor.name, operation, msg.maxRetries, msg.timeout);
|
||||||
var subscription = createSubscriptionItem(operation, msg.maxRetries, msg.timeout);
|
var subscription = createSubscriptionItem(operation, msg.maxRetries, msg.timeout);
|
||||||
if (this._state === ConnectionState.Connecting)
|
if (this._state === ConnectionState.Connecting) {
|
||||||
this._subscriptions.enqueueSubscription(subscription);
|
this._subscriptions.enqueueSubscription(subscription);
|
||||||
else
|
} else {
|
||||||
this._subscriptions.startSubscription(subscription, this._connection);
|
this._subscriptions.startSubscription(subscription, this._connection);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ConnectionState.Closed:
|
case ConnectionState.Closed:
|
||||||
msg.cb(new Error("Connection closed. Connection: " + this._esConnection.connectionName));
|
msg.cb(new Error("Connection closed. Connection: " + this._esConnection.connectionName));
|
||||||
@ -312,10 +312,11 @@ EventStoreConnectionLogicHandler.prototype._startPersistentSubscription = functi
|
|||||||
this._state === ConnectionState.Connected ? "fire" : "enqueue",
|
this._state === ConnectionState.Connected ? "fire" : "enqueue",
|
||||||
operation.constructor.name, operation, msg.maxRetries, msg.timeout);
|
operation.constructor.name, operation, msg.maxRetries, msg.timeout);
|
||||||
var subscription = createSubscriptionItem(operation, msg.maxRetries, msg.timeout);
|
var subscription = createSubscriptionItem(operation, msg.maxRetries, msg.timeout);
|
||||||
if (this._state === ConnectionState.Connecting)
|
if (this._state === ConnectionState.Connecting) {
|
||||||
this._subscriptions.enqueueSubscription(subscription);
|
this._subscriptions.enqueueSubscription(subscription);
|
||||||
else
|
} else {
|
||||||
this._subscriptions.startSubscription(subscription, this._connection);
|
this._subscriptions.startSubscription(subscription, this._connection);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ConnectionState.Closed:
|
case ConnectionState.Closed:
|
||||||
msg.cb(new Error("Connection closed. " + this._esConnection.connectionName));
|
msg.cb(new Error("Connection closed. " + this._esConnection.connectionName));
|
||||||
@ -561,8 +562,9 @@ EventStoreConnectionLogicHandler.prototype._handleTcpPackage = function(connecti
|
|||||||
default:
|
default:
|
||||||
throw new Error("Unknown InspectionDecision: " + result.decision);
|
throw new Error("Unknown InspectionDecision: " + result.decision);
|
||||||
}
|
}
|
||||||
if (this._state === ConnectionState.Connected)
|
if (this._state === ConnectionState.Connected) {
|
||||||
this._operations.scheduleWaitingOperations(connection);
|
this._operations.scheduleWaitingOperations(connection);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -609,8 +611,7 @@ EventStoreConnectionLogicHandler.prototype._reconnectTo = function(endPoints) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._state !== ConnectionState.Connected || this._connection.remoteEndPoint === endPoint)
|
if (this._state !== ConnectionState.Connected || this._connection.remoteEndPoint === endPoint) return;
|
||||||
return;
|
|
||||||
|
|
||||||
var msg = util.format("EventStoreConnection '%s': going to reconnect to [%j]. Current endpoint: [%j, L%j].",
|
var msg = util.format("EventStoreConnection '%s': going to reconnect to [%j]. Current endpoint: [%j, L%j].",
|
||||||
this._esConnection.connectionName, endPoint, this._connection.remoteEndPoint, this._connection.localEndPoint);
|
this._esConnection.connectionName, endPoint, this._connection.remoteEndPoint, this._connection.localEndPoint);
|
||||||
@ -627,30 +628,26 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() {
|
|||||||
{
|
{
|
||||||
case ConnectionState.Init: break;
|
case ConnectionState.Init: break;
|
||||||
case ConnectionState.Connecting:
|
case ConnectionState.Connecting:
|
||||||
if (this._connectingPhase === ConnectingPhase.Reconnecting && (Date.now() - this._reconnInfo.timeStamp) >= this._settings.reconnectionDelay)
|
if (this._connectingPhase === ConnectingPhase.Reconnecting && (Date.now() - this._reconnInfo.timeStamp) >= this._settings.reconnectionDelay) {
|
||||||
{
|
|
||||||
this._logDebug("TimerTick checking reconnection...");
|
this._logDebug("TimerTick checking reconnection...");
|
||||||
|
|
||||||
this._reconnInfo = {reconnectionAttempt: this._reconnInfo.reconnectionAttempt + 1, timeStamp: Date.now()};
|
this._reconnInfo = {reconnectionAttempt: this._reconnInfo.reconnectionAttempt + 1, timeStamp: Date.now()};
|
||||||
if (this._settings.maxReconnections >= 0 && this._reconnInfo.reconnectionAttempt > this._settings.maxReconnections)
|
if (this._settings.maxReconnections >= 0 && this._reconnInfo.reconnectionAttempt > this._settings.maxReconnections) {
|
||||||
this._closeConnection("Reconnection limit reached.");
|
this._closeConnection("Reconnection limit reached.");
|
||||||
else
|
} else {
|
||||||
{
|
|
||||||
this.emit('reconnecting', {});
|
this.emit('reconnecting', {});
|
||||||
this._discoverEndpoint(null);
|
this._discoverEndpoint(null);
|
||||||
}
|
}
|
||||||
}
|
} else if (this._connectingPhase === ConnectingPhase.Authentication && (Date.now() - this._authInfo.timeStamp) >= this._settings.operationTimeout) {
|
||||||
else if (this._connectingPhase === ConnectingPhase.Authentication && (Date.now() - this._authInfo.timeStamp) >= this._settings.operationTimeout)
|
|
||||||
{
|
|
||||||
this.emit('authenticationFailed', "Authentication timed out.");
|
this.emit('authenticationFailed', "Authentication timed out.");
|
||||||
if (this._clientVersion === 1) {
|
if (this._clientVersion === 1) {
|
||||||
this._goToIdentifiedState();
|
this._goToIdentifiedState();
|
||||||
} else {
|
} else {
|
||||||
this._goToConnectedState();
|
this._goToConnectedState();
|
||||||
}
|
}
|
||||||
}
|
} else if (this._connectingPhase === ConnectingPhase.Authentication || this._connectingPhase === ConnectingPhase.Connected) {
|
||||||
else if (this._connectingPhase === ConnectingPhase.Authentication || this._connectingPhase === ConnectingPhase.Connected)
|
|
||||||
this._manageHeartbeats();
|
this._manageHeartbeats();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ConnectionState.Connected:
|
case ConnectionState.Connected:
|
||||||
// operations timeouts are checked only if connection is established and check period time passed
|
// operations timeouts are checked only if connection is established and check period time passed
|
||||||
@ -677,8 +674,7 @@ EventStoreConnectionLogicHandler.prototype._manageHeartbeats = function() {
|
|||||||
if (this._connection === null) return;
|
if (this._connection === null) return;
|
||||||
|
|
||||||
var timeout = this._heartbeatInfo.isIntervalStage ? this._settings.heartbeatInterval : this._settings.heartbeatTimeout;
|
var timeout = this._heartbeatInfo.isIntervalStage ? this._settings.heartbeatInterval : this._settings.heartbeatTimeout;
|
||||||
if ((Date.now() - this._heartbeatInfo.timeStamp) < timeout)
|
if ((Date.now() - this._heartbeatInfo.timeStamp) < timeout) return;
|
||||||
return;
|
|
||||||
|
|
||||||
var packageNumber = this._packageNumber;
|
var packageNumber = this._packageNumber;
|
||||||
if (this._heartbeatInfo.lastPackageNumber !== packageNumber)
|
if (this._heartbeatInfo.lastPackageNumber !== packageNumber)
|
||||||
@ -711,15 +707,17 @@ EventStoreConnectionLogicHandler.prototype._manageHeartbeats = function() {
|
|||||||
EventStoreConnectionLogicHandler.prototype._logDebug = function(message) {
|
EventStoreConnectionLogicHandler.prototype._logDebug = function(message) {
|
||||||
if (!this._settings.verboseLogging) return;
|
if (!this._settings.verboseLogging) return;
|
||||||
|
|
||||||
if (arguments.length > 1)
|
if (arguments.length > 1) {
|
||||||
message = util.format.apply(util, Array.prototype.slice.call(arguments));
|
message = util.format.apply(util, Array.prototype.slice.call(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
this._settings.log.debug("EventStoreConnection '%s': %s", this._esConnection.connectionName, message);
|
this._settings.log.debug("EventStoreConnection '%s': %s", this._esConnection.connectionName, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
EventStoreConnectionLogicHandler.prototype._logInfo = function(message){
|
EventStoreConnectionLogicHandler.prototype._logInfo = function(message){
|
||||||
if (arguments.length > 1)
|
if (arguments.length > 1) {
|
||||||
message = util.format.apply(util, Array.prototype.slice.call(arguments));
|
message = util.format.apply(util, Array.prototype.slice.call(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
this._settings.log.info("EventStoreConnection '%s': %s", this._esConnection.connectionName, message);
|
this._settings.log.info("EventStoreConnection '%s': %s", this._esConnection.connectionName, message);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var ensure = require('../common/utils/ensure');
|
|
||||||
|
|
||||||
function Message() {
|
function Message() {
|
||||||
}
|
}
|
||||||
|
@ -110,8 +110,7 @@ OperationsManager.prototype.checkTimeoutsAndRetry = function(connection) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
OperationsManager.prototype.scheduleOperationRetry = function(operation) {
|
OperationsManager.prototype.scheduleOperationRetry = function(operation) {
|
||||||
if (!this.removeOperation(operation))
|
if (!this.removeOperation(operation)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
this._logDebug("ScheduleOperationRetry for %s.", operation);
|
this._logDebug("ScheduleOperationRetry for %s.", operation);
|
||||||
if (operation.maxRetries >= 0 && operation.retryCount >= operation.maxRetries)
|
if (operation.maxRetries >= 0 && operation.retryCount >= operation.maxRetries)
|
||||||
@ -166,8 +165,9 @@ OperationsManager.prototype.scheduleOperation = function(operation, connection)
|
|||||||
OperationsManager.prototype._logDebug = function(message) {
|
OperationsManager.prototype._logDebug = function(message) {
|
||||||
if (!this._settings.verboseLogging) return;
|
if (!this._settings.verboseLogging) return;
|
||||||
|
|
||||||
if (arguments.length > 1)
|
if (arguments.length > 1) {
|
||||||
message = util.format.apply(util, Array.prototype.slice.call(arguments));
|
message = util.format.apply(util, Array.prototype.slice.call(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
this._settings.log.debug("EventStoreConnection '%s': %s.", this._connectionName, message);
|
this._settings.log.debug("EventStoreConnection '%s': %s.", this._connectionName, message);
|
||||||
};
|
};
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
function typeName(t) {
|
function typeName(t) {
|
||||||
if (typeof t === 'function')
|
if (typeof t === 'function') return t.name;
|
||||||
return t.name;
|
if (typeof t === 'object') return t.constructor.name;
|
||||||
if (typeof t === 'object')
|
|
||||||
return t.constructor.name;
|
|
||||||
throw new TypeError('type must be a function or object, not ' + typeof t);
|
throw new TypeError('type must be a function or object, not ' + typeof t);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SimpleQueuedHandler() {
|
function SimpleQueuedHandler(log) {
|
||||||
this._handlers = {};
|
this._handlers = {};
|
||||||
this._messages = [];
|
this._messages = [];
|
||||||
this._isProcessing = false;
|
this._isProcessing = false;
|
||||||
|
this._log = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleQueuedHandler.prototype.registerHandler = function(type, handler) {
|
SimpleQueuedHandler.prototype.registerHandler = function(type, handler) {
|
||||||
var typeId = typeName(type);
|
var typeId = typeName(type);
|
||||||
|
var log = this._log;
|
||||||
this._handlers[typeId] = function (msg) {
|
this._handlers[typeId] = function (msg) {
|
||||||
try {
|
try {
|
||||||
handler(msg);
|
handler(msg);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.log('ERROR: ', e.stack);
|
log.error('handle for', type, 'failed:', e.stack);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -36,8 +36,7 @@ SimpleQueuedHandler.prototype._processQueue = function() {
|
|||||||
while(message) {
|
while(message) {
|
||||||
var typeId = typeName(message);
|
var typeId = typeName(message);
|
||||||
var handler = this._handlers[typeId];
|
var handler = this._handlers[typeId];
|
||||||
if (!handler)
|
if (!handler) throw new Error("No handler registered for message " + typeId);
|
||||||
throw new Error("No handler registered for message " + typeId);
|
|
||||||
setImmediate(handler, message);
|
setImmediate(handler, message);
|
||||||
message = this._messages.shift();
|
message = this._messages.shift();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||||
function isValidId(id) {
|
function isValidId(id) {
|
||||||
if (typeof id !== 'string') return false;
|
if (typeof id !== 'string') return false;
|
||||||
@ -26,8 +24,8 @@ function EventData(eventId, type, isJson, data, metadata) {
|
|||||||
this.eventId = eventId;
|
this.eventId = eventId;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.isJson = isJson || false;
|
this.isJson = isJson || false;
|
||||||
this.data = data || new Buffer(0);
|
this.data = data || Buffer.alloc(0);
|
||||||
this.metadata = metadata || new Buffer(0);
|
this.metadata = metadata || Buffer.alloc(0);
|
||||||
Object.freeze(this);
|
Object.freeze(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +45,7 @@ EventStoreAllCatchUpSubscription.prototype._readEventsTill = function(
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(function(done) {
|
.then(function(done) {
|
||||||
if (done || self._shouldStop)
|
if (done || self._shouldStop) return;
|
||||||
return;
|
|
||||||
return readNext();
|
return readNext();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -69,10 +68,11 @@ EventStoreAllCatchUpSubscription.prototype._tryProcess = function(e) {
|
|||||||
this._lastProcessedPosition = e.originalPosition;
|
this._lastProcessedPosition = e.originalPosition;
|
||||||
processed = true;
|
processed = true;
|
||||||
}
|
}
|
||||||
if (this._verbose)
|
if (this._verbose) {
|
||||||
this._log.debug("Catch-up Subscription to %s: %s event (%s, %d, %s @ %s).",
|
this._log.debug("Catch-up Subscription to %s: %s event (%s, %d, %s @ %s).",
|
||||||
this.streamId || '<all>', processed ? "processed" : "skipping",
|
this.streamId || '<all>', processed ? "processed" : "skipping",
|
||||||
e.originalEvent.eventStreamId, e.originalEvent.eventNumber, e.originalEvent.eventType, e.originalPosition);
|
e.originalEvent.eventStreamId, e.originalEvent.eventNumber, e.originalEvent.eventType, e.originalPosition);
|
||||||
|
}
|
||||||
return (promise && promise.then) ? promise : Promise.resolve();
|
return (promise && promise.then) ? promise : Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|
||||||
var SubscriptionDropReason = require('./subscriptionDropReason');
|
var SubscriptionDropReason = require('./subscriptionDropReason');
|
||||||
var results = require('./results');
|
|
||||||
|
|
||||||
const DefaultReadBatchSize = 500;
|
const DefaultReadBatchSize = 500;
|
||||||
const DefaultMaxPushQueueSize = 10000;
|
const DefaultMaxPushQueueSize = 10000;
|
||||||
@ -68,9 +67,9 @@ function EventStoreCatchUpSubscription(
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this._onReconnect = function() {
|
this._onReconnect = function() {
|
||||||
if (self._verbose) self._log.debug("Catch-up Subscription to %s: recovering after reconnection.", self._streamId || '<all>');
|
|
||||||
if (self._verbose) self._log.debug("Catch-up Subscription to %s: unhooking from connection.Connected.", self._streamId || '<all>');
|
if (self._verbose) self._log.debug("Catch-up Subscription to %s: unhooking from connection.Connected.", self._streamId || '<all>');
|
||||||
self._connection.removeListener('connected', self._onReconnect);
|
self._connection.removeListener('connected', self._onReconnect);
|
||||||
|
if (self._verbose) self._log.debug("Catch-up Subscription to %s: recovering after reconnection.", self._streamId || '<all>');
|
||||||
self._runSubscription();
|
self._runSubscription();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,15 +127,18 @@ EventStoreCatchUpSubscription.prototype._runSubscription = function() {
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this._stopped = false;
|
this._stopped = false;
|
||||||
|
this._isDropped = false;
|
||||||
|
this._dropData = null;
|
||||||
if (this._verbose) this._log.debug("Catch-up Subscription to %s: pulling events...", logStreamName);
|
if (this._verbose) this._log.debug("Catch-up Subscription to %s: pulling events...", logStreamName);
|
||||||
this._readEventsTill(this._connection, this._resolveLinkTos, this._userCredentials, null, null)
|
this._readEventsTill(this._connection, this._resolveLinkTos, this._userCredentials, null, null)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
if (self._shouldStop) return;
|
if (self._shouldStop) return;
|
||||||
if (self._verbose) self._log.debug("Catch-up Subscription to %s: subscribing...", logStreamName);
|
if (self._verbose) self._log.debug("Catch-up Subscription to %s: subscribing...", logStreamName);
|
||||||
if (self._streamId === '')
|
if (self._streamId === '') {
|
||||||
return self._connection.subscribeToAll(self._resolveLinkTos, self._enqueuePushedEvent.bind(self), self._serverSubscriptionDropped.bind(self), self._userCredentials);
|
return self._connection.subscribeToAll(self._resolveLinkTos, self._enqueuePushedEvent.bind(self), self._serverSubscriptionDropped.bind(self), self._userCredentials);
|
||||||
else
|
} else {
|
||||||
return self._connection.subscribeToStream(self._streamId, self._resolveLinkTos, self._enqueuePushedEvent.bind(self), self._serverSubscriptionDropped.bind(self), self._userCredentials);
|
return self._connection.subscribeToStream(self._streamId, self._resolveLinkTos, self._enqueuePushedEvent.bind(self), self._serverSubscriptionDropped.bind(self), self._userCredentials);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(function(subscription) {
|
.then(function(subscription) {
|
||||||
if (subscription === undefined) return;
|
if (subscription === undefined) return;
|
||||||
@ -155,12 +157,13 @@ EventStoreCatchUpSubscription.prototype._runSubscription = function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (self._verbose) self._log.debug("Catch-up Subscription to %s: processing live events...", logStreamName);
|
if (self._verbose) self._log.debug("Catch-up Subscription to %s: processing live events...", logStreamName);
|
||||||
if (self._liveProcessingStarted)
|
if (self._liveProcessingStarted) {
|
||||||
try {
|
try {
|
||||||
self._liveProcessingStarted(self);
|
self._liveProcessingStarted(self);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
self._log.error(e, "Catch-up Subscription to %s: liveProcessingStarted callback failed.", logStreamName);
|
self._log.error(e, "Catch-up Subscription to %s: liveProcessingStarted callback failed.", logStreamName);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (self._verbose) self._log.debug("Catch-up Subscription to %s: hooking to connection.Connected", logStreamName);
|
if (self._verbose) self._log.debug("Catch-up Subscription to %s: hooking to connection.Connected", logStreamName);
|
||||||
self._connection.on('connected', self._onReconnect);
|
self._connection.on('connected', self._onReconnect);
|
||||||
self._allowProcessing = true;
|
self._allowProcessing = true;
|
||||||
@ -169,10 +172,11 @@ EventStoreCatchUpSubscription.prototype._runSubscription = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
EventStoreCatchUpSubscription.prototype._enqueuePushedEvent = function(subscription, e) {
|
EventStoreCatchUpSubscription.prototype._enqueuePushedEvent = function(subscription, e) {
|
||||||
if (this._verbose)
|
if (this._verbose) {
|
||||||
this._log.debug("Catch-up Subscription to %s: event appeared (%s, %d, %s @ %s).",
|
this._log.debug("Catch-up Subscription to %s: event appeared (%s, %d, %s @ %s).",
|
||||||
this._streamId || '<all>',
|
this._streamId || '<all>',
|
||||||
e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition);
|
e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition);
|
||||||
|
}
|
||||||
|
|
||||||
if (this._liveQueue.length >= this.maxPushQueueSize)
|
if (this._liveQueue.length >= this.maxPushQueueSize)
|
||||||
{
|
{
|
||||||
@ -183,8 +187,7 @@ EventStoreCatchUpSubscription.prototype._enqueuePushedEvent = function(subscript
|
|||||||
|
|
||||||
this._liveQueue.push(e);
|
this._liveQueue.push(e);
|
||||||
|
|
||||||
if (this._allowProcessing)
|
if (this._allowProcessing) this._ensureProcessingPushQueue();
|
||||||
this._ensureProcessingPushQueue();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EventStoreCatchUpSubscription.prototype._serverSubscriptionDropped = function(subscription, reason, err) {
|
EventStoreCatchUpSubscription.prototype._serverSubscriptionDropped = function(subscription, reason, err) {
|
||||||
@ -196,8 +199,7 @@ EventStoreCatchUpSubscription.prototype._enqueueSubscriptionDropNotification = f
|
|||||||
if (this._dropData) return;
|
if (this._dropData) return;
|
||||||
this._dropData = {reason: reason, error: error};
|
this._dropData = {reason: reason, error: error};
|
||||||
this._liveQueue.push(new DropSubscriptionEvent());
|
this._liveQueue.push(new DropSubscriptionEvent());
|
||||||
if (this._allowProcessing)
|
if (this._allowProcessing) this._ensureProcessingPushQueue();
|
||||||
this._ensureProcessingPushQueue();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EventStoreCatchUpSubscription.prototype._ensureProcessingPushQueue = function() {
|
EventStoreCatchUpSubscription.prototype._ensureProcessingPushQueue = function() {
|
||||||
@ -244,18 +246,19 @@ EventStoreCatchUpSubscription.prototype._dropSubscription = function(reason, err
|
|||||||
if (this._isDropped) return;
|
if (this._isDropped) return;
|
||||||
|
|
||||||
this._isDropped = true;
|
this._isDropped = true;
|
||||||
if (this._verbose)
|
if (this._verbose) {
|
||||||
this._log.debug("Catch-up Subscription to %s: dropping subscription, reason: %s %s.",
|
this._log.debug("Catch-up Subscription to %s: dropping subscription, reason: %s %s.",
|
||||||
this._streamId || '<all>', reason, error);
|
this._streamId || '<all>', reason, error);
|
||||||
|
}
|
||||||
|
|
||||||
if (this._subscription)
|
if (this._subscription) this._subscription.unsubscribe();
|
||||||
this._subscription.unsubscribe();
|
if (this._subscriptionDropped) {
|
||||||
if (this._subscriptionDropped)
|
|
||||||
try {
|
try {
|
||||||
this._subscriptionDropped(this, reason, error);
|
this._subscriptionDropped(this, reason, error);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
this._log.error(e, "Catch-up Subscription to %s: subscriptionDropped callback failed.", this._streamId || '<all>');
|
this._log.error(e, "Catch-up Subscription to %s: subscriptionDropped callback failed.", this._streamId || '<all>');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
this._stopped = true;
|
this._stopped = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
var EventStoreNodeConnection = require('./eventStoreNodeConnection');
|
var EventStoreNodeConnection = require('./eventStoreNodeConnection');
|
||||||
var StaticEndpointDiscoverer = require('./core/staticEndpointDiscoverer');
|
var StaticEndpointDiscoverer = require('./core/staticEndpointDiscoverer');
|
||||||
var ClusterDnsEndPointDiscoverer = require('./core/clusterDnsEndPointDiscoverer');
|
var ClusterDiscoverer = require('./core/cluster/clusterDiscoverer');
|
||||||
var NoopLogger = require('./common/log/noopLogger');
|
var NoopLogger = require('./common/log/noopLogger');
|
||||||
var ensure = require('./common/utils/ensure');
|
var ensure = require('./common/utils/ensure');
|
||||||
|
|
||||||
var defaultConnectionSettings = {
|
const util = require('util');
|
||||||
|
const http = require('http');
|
||||||
|
const dns = require('dns');
|
||||||
|
|
||||||
|
const dnsService = {
|
||||||
|
lookup : util.promisify(dns.lookup),
|
||||||
|
ADDRCONFIG: dns.ADDRCONFIG,
|
||||||
|
V4MAPPED: dns.V4MAPPED
|
||||||
|
};
|
||||||
|
|
||||||
|
var defaultConnectionSettings = Object.freeze({
|
||||||
log: new NoopLogger(),
|
log: new NoopLogger(),
|
||||||
verboseLogging: false,
|
verboseLogging: false,
|
||||||
|
|
||||||
@ -32,9 +42,10 @@ var defaultConnectionSettings = {
|
|||||||
// Cluster Settings
|
// Cluster Settings
|
||||||
clusterDns: '',
|
clusterDns: '',
|
||||||
maxDiscoverAttempts: 10,
|
maxDiscoverAttempts: 10,
|
||||||
|
discoverDelay: 500,
|
||||||
externalGossipPort: 0,
|
externalGossipPort: 0,
|
||||||
gossipTimeout: 1000
|
gossipTimeout: 1000
|
||||||
};
|
});
|
||||||
|
|
||||||
|
|
||||||
function merge(a,b) {
|
function merge(a,b) {
|
||||||
@ -80,17 +91,17 @@ function createFromClusterDns(connectionSettings, clusterDns, externalGossipPort
|
|||||||
var mergedSettings = merge(defaultConnectionSettings, connectionSettings || {});
|
var mergedSettings = merge(defaultConnectionSettings, connectionSettings || {});
|
||||||
var clusterSettings = {
|
var clusterSettings = {
|
||||||
clusterDns: clusterDns,
|
clusterDns: clusterDns,
|
||||||
gossipSeeds: null,
|
seeds: null,
|
||||||
externalGossipPort: externalGossipPort,
|
managerExternalHttpPort: externalGossipPort,
|
||||||
maxDiscoverAttempts: mergedSettings.maxDiscoverAttempts,
|
maxDiscoverAttempts: mergedSettings.maxDiscoverAttempts,
|
||||||
|
discoverDelay: mergedSettings.discoverDelay,
|
||||||
gossipTimeout: mergedSettings.gossipTimeout
|
gossipTimeout: mergedSettings.gossipTimeout
|
||||||
};
|
};
|
||||||
var endPointDiscoverer = new ClusterDnsEndPointDiscoverer(mergedSettings.log,
|
var endPointDiscoverer = new ClusterDiscoverer(
|
||||||
clusterSettings.clusterDns,
|
mergedSettings.log,
|
||||||
clusterSettings.maxDiscoverAttempts,
|
clusterSettings,
|
||||||
clusterSettings.externalGossipPort,
|
dnsService,
|
||||||
clusterSettings.gossipSeeds,
|
http
|
||||||
clusterSettings.gossipTimeout
|
|
||||||
);
|
);
|
||||||
return new EventStoreNodeConnection(mergedSettings, clusterSettings, endPointDiscoverer, connectionName);
|
return new EventStoreNodeConnection(mergedSettings, clusterSettings, endPointDiscoverer, connectionName);
|
||||||
}
|
}
|
||||||
@ -101,17 +112,17 @@ function createFromGossipSeeds(connectionSettings, gossipSeeds, connectionName)
|
|||||||
var mergedSettings = merge(defaultConnectionSettings, connectionSettings || {});
|
var mergedSettings = merge(defaultConnectionSettings, connectionSettings || {});
|
||||||
var clusterSettings = {
|
var clusterSettings = {
|
||||||
clusterDns: '',
|
clusterDns: '',
|
||||||
gossipSeeds: gossipSeeds,
|
seeds: gossipSeeds,
|
||||||
externalGossipPort: 0,
|
externalGossipPort: 0,
|
||||||
maxDiscoverAttempts: mergedSettings.maxDiscoverAttempts,
|
maxDiscoverAttempts: mergedSettings.maxDiscoverAttempts,
|
||||||
|
discoverDelay: mergedSettings.discoverDelay,
|
||||||
gossipTimeout: mergedSettings.gossipTimeout
|
gossipTimeout: mergedSettings.gossipTimeout
|
||||||
};
|
};
|
||||||
var endPointDiscoverer = new ClusterDnsEndPointDiscoverer(mergedSettings.log,
|
var endPointDiscoverer = new ClusterDiscoverer(
|
||||||
clusterSettings.clusterDns,
|
mergedSettings.log,
|
||||||
clusterSettings.maxDiscoverAttempts,
|
clusterSettings,
|
||||||
clusterSettings.externalGossipPort,
|
dnsService,
|
||||||
clusterSettings.gossipSeeds,
|
http
|
||||||
clusterSettings.gossipTimeout
|
|
||||||
);
|
);
|
||||||
return new EventStoreNodeConnection(mergedSettings, clusterSettings, endPointDiscoverer, connectionName);
|
return new EventStoreNodeConnection(mergedSettings, clusterSettings, endPointDiscoverer, connectionName);
|
||||||
}
|
}
|
||||||
@ -121,6 +132,27 @@ function createFromGossipSeeds(connectionSettings, gossipSeeds, connectionName)
|
|||||||
* @public
|
* @public
|
||||||
* @alias createConnection
|
* @alias createConnection
|
||||||
* @param {object} settings
|
* @param {object} settings
|
||||||
|
* @param {boolean} [settings.verboseLogging=false]
|
||||||
|
* @param {number} [settings.maxQueueSize=5000]
|
||||||
|
* @param {number} [settings.maxConcurrentItems=5000]
|
||||||
|
* @param {number} [settings.maxRetries=10]
|
||||||
|
* @param {number} [settings.maxReconnections=10]
|
||||||
|
* @param {boolean} [settings.requireMaster=true]
|
||||||
|
* @param {number} [settings.reconnectionDelay=100]
|
||||||
|
* @param {number} [settings.operationTimeout=7000]
|
||||||
|
* @param {number} [settings.operationTimeoutCheckPeriod=1000]
|
||||||
|
* @param {object} [settings.defaultUserCredentials=null] - The default user credentials to use for requests
|
||||||
|
* @param {boolean} [settings.useSslConnection=false] - Whether to use SSL or not
|
||||||
|
* @param {object} [settings.targetHost=null]
|
||||||
|
* @param {boolean} [settings.validateServer=false]
|
||||||
|
* @param {boolean} [settings.failOnNoServerResponse=false]
|
||||||
|
* @param {number} [settings.heartbeatInterval=750]
|
||||||
|
* @param {number} [settings.heartbeatTimeout=1500]
|
||||||
|
* @param {number} [settings.clientConnectionTimeout=1000]
|
||||||
|
* @param {string} [settings.clusterDns='']
|
||||||
|
* @param {number} [settings.maxDiscoverAttempts=10]
|
||||||
|
* @param {number} [settings.externalGossipPort=0]
|
||||||
|
* @param {number} [settings.gossipTimeout=1000]
|
||||||
* @param {string|object|array} endPointOrGossipSeeds
|
* @param {string|object|array} endPointOrGossipSeeds
|
||||||
* @param {string} [connectionName]
|
* @param {string} [connectionName]
|
||||||
* @returns {EventStoreNodeConnection}
|
* @returns {EventStoreNodeConnection}
|
||||||
|
@ -140,8 +140,7 @@ EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVer
|
|||||||
ensure.notNullOrEmpty(stream, "stream");
|
ensure.notNullOrEmpty(stream, "stream");
|
||||||
ensure.isLongOrInteger(expectedVersion, "expectedVersion");
|
ensure.isLongOrInteger(expectedVersion, "expectedVersion");
|
||||||
expectedVersion = Long.fromValue(expectedVersion);
|
expectedVersion = Long.fromValue(expectedVersion);
|
||||||
if (!Array.isArray(events))
|
if (!Array.isArray(events)) events = [events];
|
||||||
events = [events];
|
|
||||||
ensure.isArrayOf(EventData, events, "events");
|
ensure.isArrayOf(EventData, events, "events");
|
||||||
userCredentials = userCredentials || null;
|
userCredentials = userCredentials || null;
|
||||||
|
|
||||||
@ -413,8 +412,7 @@ EventStoreNodeConnection.prototype.subscribeToStream = function(
|
|||||||
) {
|
) {
|
||||||
ensure.notNullOrEmpty(stream, "stream");
|
ensure.notNullOrEmpty(stream, "stream");
|
||||||
ensure.isTypeOf(Function, eventAppeared, "eventAppeared");
|
ensure.isTypeOf(Function, eventAppeared, "eventAppeared");
|
||||||
if (subscriptionDropped)
|
if (subscriptionDropped) ensure.isTypeOf(Function, subscriptionDropped, "subscriptionDropped");
|
||||||
ensure.isTypeOf(Function, subscriptionDropped, "subscriptionDropped");
|
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
return new Promise(function(resolve,reject) {
|
return new Promise(function(resolve,reject) {
|
||||||
@ -641,8 +639,9 @@ EventStoreNodeConnection.prototype.setStreamMetadataRaw = function(
|
|||||||
stream, expectedMetastreamVersion, metadata, userCredentials
|
stream, expectedMetastreamVersion, metadata, userCredentials
|
||||||
) {
|
) {
|
||||||
ensure.notNullOrEmpty(stream, "stream");
|
ensure.notNullOrEmpty(stream, "stream");
|
||||||
if (systemStreams.isMetastream(stream))
|
if (systemStreams.isMetastream(stream)) {
|
||||||
throw new Error(util.format("Setting metadata for metastream '%s' is not supported.", stream));
|
throw new Error(util.format("Setting metadata for metastream '%s' is not supported.", stream));
|
||||||
|
}
|
||||||
ensure.isLongOrInteger(expectedMetastreamVersion, "expectedMetastreamVersion");
|
ensure.isLongOrInteger(expectedMetastreamVersion, "expectedMetastreamVersion");
|
||||||
expectedMetastreamVersion = Long.fromValue(expectedMetastreamVersion);
|
expectedMetastreamVersion = Long.fromValue(expectedMetastreamVersion);
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -651,7 +650,7 @@ EventStoreNodeConnection.prototype.setStreamMetadataRaw = function(
|
|||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}
|
}
|
||||||
var data = metadata ? new Buffer(JSON.stringify(metadata)) : null;
|
var data = metadata ? Buffer.from(JSON.stringify(metadata)) : null;
|
||||||
var metaevent = new EventData(uuid.v4(), systemEventTypes.StreamMetadata, true, data, null);
|
var metaevent = new EventData(uuid.v4(), systemEventTypes.StreamMetadata, true, data, null);
|
||||||
self._enqueueOperation(
|
self._enqueueOperation(
|
||||||
new AppendToStreamOperation(self._settings.log, cb, self._settings.requireMaster,
|
new AppendToStreamOperation(self._settings.log, cb, self._settings.requireMaster,
|
||||||
@ -713,7 +712,7 @@ EventStoreNodeConnection.prototype._enqueueOperation = function(operation) {
|
|||||||
var message = new messages.StartOperationMessage(operation, self._settings.maxRetries, self._settings.operationTimeout);
|
var message = new messages.StartOperationMessage(operation, self._settings.maxRetries, self._settings.operationTimeout);
|
||||||
function tryEnqueue() {
|
function tryEnqueue() {
|
||||||
if (self._handler.totalOperationCount >= self._settings.maxQueueSize) {
|
if (self._handler.totalOperationCount >= self._settings.maxQueueSize) {
|
||||||
setImmediate(tryEnqueue);
|
setTimeout(tryEnqueue, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self._handler.enqueueMessage(message);
|
self._handler.enqueueMessage(message);
|
||||||
|
@ -38,7 +38,7 @@ EventStorePersistentSubscriptionBase.prototype.start = function() {
|
|||||||
return this._startSubscription(this._subscriptionId, this._streamId, this._bufferSize, this._userCredentials,
|
return this._startSubscription(this._subscriptionId, this._streamId, this._bufferSize, this._userCredentials,
|
||||||
this._onEventAppeared.bind(this), this._onSubscriptionDropped.bind(this), this._settings)
|
this._onEventAppeared.bind(this), this._onSubscriptionDropped.bind(this), this._settings)
|
||||||
.then(function(subscription) {
|
.then(function(subscription) {
|
||||||
console.log('Subscription started.');
|
self._log.debug('Subscription started.');
|
||||||
self._subscription = subscription;
|
self._subscription = subscription;
|
||||||
return self;
|
return self;
|
||||||
});
|
});
|
||||||
@ -56,8 +56,7 @@ EventStorePersistentSubscriptionBase.prototype.acknowledge = function(events) {
|
|||||||
ensure.notNull(events, "events");
|
ensure.notNull(events, "events");
|
||||||
|
|
||||||
if (this._subscription === null) throw new Error("Invalid operation. Subscription is not active yet.");
|
if (this._subscription === null) throw new Error("Invalid operation. Subscription is not active yet.");
|
||||||
if (!Array.isArray(events))
|
if (!Array.isArray(events)) events = [events];
|
||||||
events = [events];
|
|
||||||
var ids = events.map(function(x) { return x.originalEvent.eventId; });
|
var ids = events.map(function(x) { return x.originalEvent.eventId; });
|
||||||
this._subscription.notifyEventsProcessed(ids);
|
this._subscription.notifyEventsProcessed(ids);
|
||||||
};
|
};
|
||||||
@ -73,8 +72,7 @@ EventStorePersistentSubscriptionBase.prototype.fail = function(events, action, r
|
|||||||
ensure.notNull(reason, "reason");
|
ensure.notNull(reason, "reason");
|
||||||
|
|
||||||
if (this._subscription === null) throw new Error("Invalid operation. Subscription is not active yet.");
|
if (this._subscription === null) throw new Error("Invalid operation. Subscription is not active yet.");
|
||||||
if (!Array.isArray(events))
|
if (!Array.isArray(events)) events = [events];
|
||||||
events = [events];
|
|
||||||
var ids = events.map(function(x) { return x.originalEvent.eventId; });
|
var ids = events.map(function(x) { return x.originalEvent.eventId; });
|
||||||
this._subscription.notifyEventsFailed(ids, action, reason);
|
this._subscription.notifyEventsFailed(ids, action, reason);
|
||||||
};
|
};
|
||||||
@ -145,12 +143,12 @@ EventStorePersistentSubscriptionBase.prototype._processQueue = function() {
|
|||||||
return self._eventAppeared(self, ev);
|
return self._eventAppeared(self, ev);
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
if(self._autoAck)
|
if(self._autoAck) self._subscription.notifyEventsProcessed([ev.originalEvent.eventId]);
|
||||||
self._subscription.notifyEventsProcessed([ev.originalEvent.eventId]);
|
if (self._verbose) {
|
||||||
if (self._verbose)
|
|
||||||
self._log.debug("Persistent Subscription to %s: processed event (%s, %d, %s @ %d).",
|
self._log.debug("Persistent Subscription to %s: processed event (%s, %d, %s @ %d).",
|
||||||
self._streamId, ev.originalEvent.eventStreamId, ev.originalEvent.eventNumber, ev.originalEvent.eventType,
|
self._streamId, ev.originalEvent.eventStreamId, ev.originalEvent.eventNumber, ev.originalEvent.eventType,
|
||||||
ev.originalEventNumber);
|
ev.originalEventNumber);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
//TODO GFY should we autonak here?
|
//TODO GFY should we autonak here?
|
||||||
@ -168,12 +166,12 @@ EventStorePersistentSubscriptionBase.prototype._dropSubscription = function(reas
|
|||||||
if (!this._isDropped)
|
if (!this._isDropped)
|
||||||
{
|
{
|
||||||
this._isDropped = true;
|
this._isDropped = true;
|
||||||
if (this._verbose)
|
if (this._verbose) {
|
||||||
this._log.debug("Persistent Subscription to %s: dropping subscription, reason: %s %s.",
|
this._log.debug("Persistent Subscription to %s: dropping subscription, reason: %s %s.",
|
||||||
this._streamId, reason, error);
|
this._streamId, reason, error);
|
||||||
|
}
|
||||||
|
|
||||||
if (this._subscription !== null)
|
if (this._subscription !== null) this._subscription.unsubscribe();
|
||||||
this._subscription.unsubscribe();
|
|
||||||
if (this._subscriptionDropped !== null) {
|
if (this._subscriptionDropped !== null) {
|
||||||
try {
|
try {
|
||||||
this._subscriptionDropped(this, reason, error);
|
this._subscriptionDropped(this, reason, error);
|
||||||
|
@ -49,19 +49,18 @@ EventStoreStreamCatchUpSubscription.prototype._readEventsTill = function(
|
|||||||
.then(function() {
|
.then(function() {
|
||||||
self._nextReadEventNumber = slice.nextEventNumber;
|
self._nextReadEventNumber = slice.nextEventNumber;
|
||||||
var done = Promise.resolve(lastEventNumber === null ? slice.isEndOfStream : slice.nextEventNumber.compare(lastEventNumber) > 0);
|
var done = Promise.resolve(lastEventNumber === null ? slice.isEndOfStream : slice.nextEventNumber.compare(lastEventNumber) > 0);
|
||||||
if (!done && slice.isEndOfStream)
|
if (!done && slice.isEndOfStream) return delay(100, false);
|
||||||
return delay(100, false);
|
|
||||||
return done;
|
return done;
|
||||||
});
|
});
|
||||||
break;
|
|
||||||
case SliceReadStatus.StreamNotFound:
|
case SliceReadStatus.StreamNotFound:
|
||||||
if (lastEventNumber && lastEventNumber.compare(-1) !== 0)
|
if (lastEventNumber && lastEventNumber.compare(-1) !== 0) {
|
||||||
throw new Error(util.format("Impossible: stream %s disappeared in the middle of catching up subscription.", self.streamId));
|
throw new Error(util.format("Impossible: stream %s disappeared in the middle of catching up subscription.", self.streamId));
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
case SliceReadStatus.StreamDeleted:
|
case SliceReadStatus.StreamDeleted:
|
||||||
throw new Error("Stream deleted: " + self.streamId);
|
throw new Error("Stream deleted: " + self.streamId);
|
||||||
default:
|
default:
|
||||||
throw new Error("Unexpected StreamEventsSlice.Status: %s.", slice.status);
|
throw new Error(util.format("Unexpected StreamEventsSlice.Status: %s.", slice.status));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(function(done) {
|
.then(function(done) {
|
||||||
@ -72,9 +71,10 @@ EventStoreStreamCatchUpSubscription.prototype._readEventsTill = function(
|
|||||||
}
|
}
|
||||||
return readNext()
|
return readNext()
|
||||||
.then(function() {
|
.then(function() {
|
||||||
if (self._verbose)
|
if (self._verbose) {
|
||||||
self._log.debug("Catch-up Subscription to %s: finished reading events, nextReadEventNumber = %d.",
|
self._log.debug("Catch-up Subscription to %s: finished reading events, nextReadEventNumber = %d.",
|
||||||
self.isSubscribedToAll ? '<all>' : self.streamId, self._nextReadEventNumber);
|
self.isSubscribedToAll ? '<all>' : self.streamId, self._nextReadEventNumber);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -86,10 +86,11 @@ EventStoreStreamCatchUpSubscription.prototype._tryProcess = function(e) {
|
|||||||
this._lastProcessedEventNumber = e.originalEventNumber;
|
this._lastProcessedEventNumber = e.originalEventNumber;
|
||||||
processed = true;
|
processed = true;
|
||||||
}
|
}
|
||||||
if (this._verbose)
|
if (this._verbose) {
|
||||||
this._log.debug("Catch-up Subscription to %s: %s event (%s, %d, %s @ %d).",
|
this._log.debug("Catch-up Subscription to %s: %s event (%s, %d, %s @ %d).",
|
||||||
this.isSubscribedToAll ? '<all>' : this.streamId, processed ? "processed" : "skipping",
|
this.isSubscribedToAll ? '<all>' : this.streamId, processed ? "processed" : "skipping",
|
||||||
e.originalEvent.eventStreamId, e.originalEvent.eventNumber, e.originalEvent.eventType, e.originalEventNumber);
|
e.originalEvent.eventStreamId, e.originalEvent.eventNumber, e.originalEvent.eventType, e.originalEventNumber);
|
||||||
|
}
|
||||||
return (promise && promise.then) ? promise : Promise.resolve();
|
return (promise && promise.then) ? promise : Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
module.exports = function GossipSeed(endPoint, hostName) {
|
function GossipSeed(endPoint, hostName, hostHeader) {
|
||||||
if (typeof endPoint !== 'object' || !endPoint.host || !endPoint.port) throw new TypeError('endPoint must be have host and port properties.');
|
if (typeof endPoint !== 'object' || !endPoint.host || !endPoint.port) throw new TypeError('endPoint must be have host and port properties.');
|
||||||
this.endPoint = endPoint;
|
this.endPoint = endPoint;
|
||||||
this.hostName = hostName;
|
this.hostName = hostName;
|
||||||
|
this.hostHeader = hostHeader;
|
||||||
Object.freeze(this);
|
Object.freeze(this);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
module.exports = GossipSeed;
|
File diff suppressed because it is too large
Load Diff
@ -35,12 +35,12 @@ message EventRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message ResolvedIndexedEvent {
|
message ResolvedIndexedEvent {
|
||||||
required EventRecord event = 1;
|
optional EventRecord event = 1;
|
||||||
optional EventRecord link = 2;
|
optional EventRecord link = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ResolvedEvent {
|
message ResolvedEvent {
|
||||||
required EventRecord event = 1;
|
optional EventRecord event = 1;
|
||||||
optional EventRecord link = 2;
|
optional EventRecord link = 2;
|
||||||
required int64 commit_position = 3;
|
required int64 commit_position = 3;
|
||||||
required int64 prepare_position = 4;
|
required int64 prepare_position = 4;
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
const PersistentSubscriptionNakEventAction = {
|
const PersistentSubscriptionNakEventAction = Object.freeze({
|
||||||
Unknown: 0,
|
Unknown: 0,
|
||||||
Park: 1,
|
Park: 1,
|
||||||
Retry: 2,
|
Retry: 2,
|
||||||
Skip: 3,
|
Skip: 3,
|
||||||
Stop: 4
|
Stop: 4,
|
||||||
};
|
isValid: function(value) {
|
||||||
|
for(var k in PersistentSubscriptionNakEventAction) {
|
||||||
|
if (PersistentSubscriptionNakEventAction[k] === value) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = PersistentSubscriptionNakEventAction;
|
module.exports = PersistentSubscriptionNakEventAction;
|
||||||
module.exports.isValid = function(value) {
|
|
||||||
for(var k in PersistentSubscriptionNakEventAction)
|
|
||||||
if (PersistentSubscriptionNakEventAction[k] === value) return true;
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
const https = require('https');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const ProjectionCommandFailedError = require('../errors/projectionCommandFailedError');
|
const ProjectionCommandFailedError = require('../errors/projectionCommandFailedError');
|
||||||
@ -31,6 +32,10 @@ ProjectionsClient.prototype.abort = function(httpEndPoint, name, userCredentials
|
|||||||
return this.sendPost(httpEndPoint + '/projection/' + name + '/command/abort', '', userCredentials, HTTP_OK);
|
return this.sendPost(httpEndPoint + '/projection/' + name + '/command/abort', '', userCredentials, HTTP_OK);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ProjectionsClient.prototype.reset = function(httpEndPoint, name, userCredentials) {
|
||||||
|
return this.sendPost(httpEndPoint + '/projection/' + name + '/command/reset', '', userCredentials, HTTP_OK);
|
||||||
|
};
|
||||||
|
|
||||||
ProjectionsClient.prototype.createOneTime = function(httpEndPoint, query, userCredentials) {
|
ProjectionsClient.prototype.createOneTime = function(httpEndPoint, query, userCredentials) {
|
||||||
return this.sendPost(httpEndPoint + '/projections/onetime?type=JS', query, userCredentials, HTTP_CREATED);
|
return this.sendPost(httpEndPoint + '/projections/onetime?type=JS', query, userCredentials, HTTP_CREATED);
|
||||||
};
|
};
|
||||||
@ -40,7 +45,7 @@ ProjectionsClient.prototype.createTransient = function(httpEndPoint, name, query
|
|||||||
};
|
};
|
||||||
|
|
||||||
ProjectionsClient.prototype.createContinuous = function(httpEndPoint, name, query, trackEmittedStreams, userCredentials) {
|
ProjectionsClient.prototype.createContinuous = function(httpEndPoint, name, query, trackEmittedStreams, userCredentials) {
|
||||||
return this.sendPost(httpEndPoint + '/projections/continuous?name=' + name + '&type=JS&emit=1&trackEmittedStreams=' + trackEmittedStreams, query, userCredentials, HTTP_CREATED);
|
return this.sendPost(httpEndPoint + '/projections/continuous?name=' + name + '&type=JS&emit=1&trackemittedstreams=' + trackEmittedStreams, query, userCredentials, HTTP_CREATED);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProjectionsClient.prototype.listAll = function(httpEndPoint, userCredentials) {
|
ProjectionsClient.prototype.listAll = function(httpEndPoint, userCredentials) {
|
||||||
@ -102,12 +107,13 @@ ProjectionsClient.prototype.updateQuery = function(httpEndPoint, name, query, us
|
|||||||
return this.sendPut(httpEndPoint + '/projection/' + name + '/query?type=JS', query, userCredentials, HTTP_OK);
|
return this.sendPut(httpEndPoint + '/projection/' + name + '/query?type=JS', query, userCredentials, HTTP_OK);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProjectionsClient.prototype.delete = function(httpEndPoint, name, deleteEmittedStreams, userCredentials) {
|
ProjectionsClient.prototype.delete = function(httpEndPoint, name, deleteEmittedStreams, deleteStateStream, deleteCheckpointStream, userCredentials) {
|
||||||
return this.sendDelete(httpEndPoint + '/projection/' + name + '?deleteEmittedStreams=' + deleteEmittedStreams, userCredentials, HTTP_OK);
|
return this.sendDelete(httpEndPoint + '/projection/' + name + '?deleteStateStream=' + deleteStateStream + '&deleteCheckpointStream=' + deleteCheckpointStream + '&deleteEmittedStreams=' + deleteEmittedStreams, '', userCredentials, HTTP_OK);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProjectionsClient.prototype.request = function(method, _url, data, userCredentials, expectedCode) {
|
ProjectionsClient.prototype.request = function(method, _url, data, userCredentials, expectedCode) {
|
||||||
const options = url.parse(_url);
|
const options = url.parse(_url);
|
||||||
|
const httplib = options.protocol === 'https:' ? https : http;
|
||||||
options.method = method;
|
options.method = method;
|
||||||
if (userCredentials) {
|
if (userCredentials) {
|
||||||
options.auth = [userCredentials.username, userCredentials.password].join(':');
|
options.auth = [userCredentials.username, userCredentials.password].join(':');
|
||||||
@ -117,7 +123,7 @@ ProjectionsClient.prototype.request = function(method, _url, data, userCredentia
|
|||||||
const timeout = setTimeout(function () {
|
const timeout = setTimeout(function () {
|
||||||
reject(new Error(util.format('Request timed out for %s on %s', method, _url)))
|
reject(new Error(util.format('Request timed out for %s on %s', method, _url)))
|
||||||
}, self._operationTimeout);
|
}, self._operationTimeout);
|
||||||
const req = http.request(options, function (res) {
|
const req = httplib.request(options, function (res) {
|
||||||
const hasExpectedCode = res.statusCode === expectedCode;
|
const hasExpectedCode = res.statusCode === expectedCode;
|
||||||
var result = '';
|
var result = '';
|
||||||
res.setEncoding('utf8');
|
res.setEncoding('utf8');
|
||||||
@ -139,6 +145,7 @@ ProjectionsClient.prototype.request = function(method, _url, data, userCredentia
|
|||||||
});
|
});
|
||||||
req.on('error', reject);
|
req.on('error', reject);
|
||||||
if (data) {
|
if (data) {
|
||||||
|
req.setHeader('Content-Length', data.length);
|
||||||
req.setHeader('Content-Type', 'application/json');
|
req.setHeader('Content-Type', 'application/json');
|
||||||
req.write(data);
|
req.write(data);
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,16 @@ ProjectionsManager.prototype.abort = function(name, userCredentials) {
|
|||||||
return this._client.abort(this._httpEndPoint, name, userCredentials);
|
return this._client.abort(this._httpEndPoint, name, userCredentials);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset a projection. (This will re-emit events, streams that are written to from the projection will also be soft deleted)
|
||||||
|
* @param name The name of the projection.
|
||||||
|
* @param userCredentials Credentials for a user with permission to reset a projection.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
ProjectionsManager.prototype.reset = function(name, userCredentials) {
|
||||||
|
return this._client.reset(this._httpEndPoint, name, userCredentials);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a one-time query.
|
* Creates a one-time query.
|
||||||
* @param query The JavaScript source code for the query.
|
* @param query The JavaScript source code for the query.
|
||||||
@ -192,11 +202,13 @@ ProjectionsManager.prototype.updateQuery = function(name, query, userCredentials
|
|||||||
* Updates the definition of a query.
|
* Updates the definition of a query.
|
||||||
* @param name The name of the projection.
|
* @param name The name of the projection.
|
||||||
* @param deleteEmittedStreams Whether to delete the streams that were emitted by this projection.
|
* @param deleteEmittedStreams Whether to delete the streams that were emitted by this projection.
|
||||||
|
* @param deleteStateStream Where to delete the state stream for this projection
|
||||||
|
* @param deleteCheckpointStream Where to delete the checkpoint stream for this projection
|
||||||
* @param userCredentials Credentials for a user with permission to delete a projection.
|
* @param userCredentials Credentials for a user with permission to delete a projection.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
ProjectionsManager.prototype.delete = function(name, deleteEmittedStreams, userCredentials) {
|
ProjectionsManager.prototype.delete = function(name, deleteEmittedStreams, deleteStateStream, deleteCheckpointStream, userCredentials) {
|
||||||
return this._client.delete(this._httpEndPoint, name, deleteEmittedStreams, userCredentials);
|
return this._client.delete(this._httpEndPoint, name, deleteEmittedStreams, deleteStateStream, deleteCheckpointStream, userCredentials);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = ProjectionsManager;
|
module.exports = ProjectionsManager;
|
@ -1,6 +1,6 @@
|
|||||||
const ReadDirection = {
|
const ReadDirection = Object.freeze({
|
||||||
Forward: 'forward',
|
Forward: 'forward',
|
||||||
Backward: 'backward'
|
Backward: 'backward'
|
||||||
};
|
});
|
||||||
|
|
||||||
module.exports = ReadDirection;
|
module.exports = ReadDirection;
|
@ -19,10 +19,12 @@ function Position(commitPosition, preparePosition) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Position.prototype.compareTo = function(other) {
|
Position.prototype.compareTo = function(other) {
|
||||||
if (this.commitPosition.lt(other.commitPosition) || (this.commitPosition.eq(other.commitPosition)&& this.preparePosition.lt(other.preparePosition)))
|
if (this.commitPosition.lt(other.commitPosition) || (this.commitPosition.eq(other.commitPosition)&& this.preparePosition.lt(other.preparePosition))) {
|
||||||
return -1;
|
return -1;
|
||||||
if (this.commitPosition.gt(other.commitPosition) || (this.commitPosition.eq(other.commitPosition) && this.preparePosition.gt(other.preparePosition)))
|
}
|
||||||
|
if (this.commitPosition.gt(other.commitPosition) || (this.commitPosition.eq(other.commitPosition) && this.preparePosition.gt(other.preparePosition))) {
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,13 +35,12 @@ Position.prototype.toString = function() {
|
|||||||
Position.start = new Position(0,0);
|
Position.start = new Position(0,0);
|
||||||
Position.end = new Position(-1,-1);
|
Position.end = new Position(-1,-1);
|
||||||
|
|
||||||
const EventReadStatus = {
|
const EventReadStatus = Object.freeze({
|
||||||
Success: 'success',
|
Success: 'success',
|
||||||
NotFound: 'notFound',
|
NotFound: 'notFound',
|
||||||
NoStream: 'noStream',
|
NoStream: 'noStream',
|
||||||
StreamDeleted: 'streamDeleted'
|
StreamDeleted: 'streamDeleted'
|
||||||
};
|
});
|
||||||
Object.freeze(EventReadStatus);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} ev
|
* @param {object} ev
|
||||||
@ -48,6 +49,7 @@ Object.freeze(EventReadStatus);
|
|||||||
* @property {string} eventId
|
* @property {string} eventId
|
||||||
* @property {Long} eventNumber
|
* @property {Long} eventNumber
|
||||||
* @property {string} eventType
|
* @property {string} eventType
|
||||||
|
* @property {Date} created
|
||||||
* @property {number} createdEpoch
|
* @property {number} createdEpoch
|
||||||
* @property {?Buffer} data
|
* @property {?Buffer} data
|
||||||
* @property {?Buffer} metadata
|
* @property {?Buffer} metadata
|
||||||
@ -60,8 +62,8 @@ function RecordedEvent(ev) {
|
|||||||
this.eventType = ev.eventType;
|
this.eventType = ev.eventType;
|
||||||
this.created = new Date(ev.createdEpoch ? ev.createdEpoch.toNumber() : 0);
|
this.created = new Date(ev.createdEpoch ? ev.createdEpoch.toNumber() : 0);
|
||||||
this.createdEpoch = ev.createdEpoch ? ev.createdEpoch.toNumber() : 0;
|
this.createdEpoch = ev.createdEpoch ? ev.createdEpoch.toNumber() : 0;
|
||||||
this.data = ev.data ? ev.data : new Buffer(0);
|
this.data = ev.data ? ev.data : Buffer.alloc(0);
|
||||||
this.metadata = ev.metadata ? ev.metadata : new Buffer(0);
|
this.metadata = ev.metadata ? ev.metadata : Buffer.alloc(0);
|
||||||
this.isJson = ev.dataContentType === 1;
|
this.isJson = ev.dataContentType === 1;
|
||||||
Object.freeze(this);
|
Object.freeze(this);
|
||||||
}
|
}
|
||||||
@ -204,12 +206,11 @@ function RawStreamMetadataResult(stream, isStreamDeleted, metastreamVersion, str
|
|||||||
Object.freeze(this);
|
Object.freeze(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PersistentSubscriptionCreateStatus = {
|
const PersistentSubscriptionCreateStatus = Object.freeze({
|
||||||
Success: 'success',
|
Success: 'success',
|
||||||
NotFound: 'notFound',
|
NotFound: 'notFound',
|
||||||
Failure: 'failure'
|
Failure: 'failure'
|
||||||
};
|
});
|
||||||
Object.freeze(PersistentSubscriptionCreateStatus);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} status
|
* @param {string} status
|
||||||
@ -221,13 +222,12 @@ function PersistentSubscriptionCreateResult(status) {
|
|||||||
Object.freeze(this);
|
Object.freeze(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PersistentSubscriptionUpdateStatus = {
|
const PersistentSubscriptionUpdateStatus = Object.freeze({
|
||||||
Success: 'success',
|
Success: 'success',
|
||||||
NotFound: 'notFound',
|
NotFound: 'notFound',
|
||||||
Failure: 'failure',
|
Failure: 'failure',
|
||||||
AccessDenied: 'accessDenied'
|
AccessDenied: 'accessDenied'
|
||||||
};
|
});
|
||||||
Object.freeze(PersistentSubscriptionUpdateStatus);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} status
|
* @param {string} status
|
||||||
@ -239,11 +239,10 @@ function PersistentSubscriptionUpdateResult(status) {
|
|||||||
Object.freeze(this);
|
Object.freeze(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PersistentSubscriptionDeleteStatus = {
|
const PersistentSubscriptionDeleteStatus = Object.freeze({
|
||||||
Success: 'success',
|
Success: 'success',
|
||||||
Failure: 'failure'
|
Failure: 'failure'
|
||||||
};
|
});
|
||||||
Object.freeze(PersistentSubscriptionDeleteStatus);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} status
|
* @param {string} status
|
||||||
@ -256,18 +255,18 @@ function PersistentSubscriptionDeleteResult(status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exports Constructors
|
// Exports Constructors
|
||||||
module.exports.Position = Position;
|
exports.Position = Position;
|
||||||
module.exports.ResolvedEvent = ResolvedEvent;
|
exports.ResolvedEvent = ResolvedEvent;
|
||||||
module.exports.EventReadStatus = EventReadStatus;
|
exports.EventReadStatus = EventReadStatus;
|
||||||
module.exports.EventReadResult = EventReadResult;
|
exports.EventReadResult = EventReadResult;
|
||||||
module.exports.WriteResult = WriteResult;
|
exports.WriteResult = WriteResult;
|
||||||
module.exports.StreamEventsSlice = StreamEventsSlice;
|
exports.StreamEventsSlice = StreamEventsSlice;
|
||||||
module.exports.AllEventsSlice = AllEventsSlice;
|
exports.AllEventsSlice = AllEventsSlice;
|
||||||
module.exports.DeleteResult = DeleteResult;
|
exports.DeleteResult = DeleteResult;
|
||||||
module.exports.RawStreamMetadataResult = RawStreamMetadataResult;
|
exports.RawStreamMetadataResult = RawStreamMetadataResult;
|
||||||
module.exports.PersistentSubscriptionCreateResult = PersistentSubscriptionCreateResult;
|
exports.PersistentSubscriptionCreateResult = PersistentSubscriptionCreateResult;
|
||||||
module.exports.PersistentSubscriptionCreateStatus = PersistentSubscriptionCreateStatus;
|
exports.PersistentSubscriptionCreateStatus = PersistentSubscriptionCreateStatus;
|
||||||
module.exports.PersistentSubscriptionUpdateResult = PersistentSubscriptionUpdateResult;
|
exports.PersistentSubscriptionUpdateResult = PersistentSubscriptionUpdateResult;
|
||||||
module.exports.PersistentSubscriptionUpdateStatus = PersistentSubscriptionUpdateStatus;
|
exports.PersistentSubscriptionUpdateStatus = PersistentSubscriptionUpdateStatus;
|
||||||
module.exports.PersistentSubscriptionDeleteResult = PersistentSubscriptionDeleteResult;
|
exports.PersistentSubscriptionDeleteResult = PersistentSubscriptionDeleteResult;
|
||||||
module.exports.PersistentSubscriptionDeleteStatus = PersistentSubscriptionDeleteStatus;
|
exports.PersistentSubscriptionDeleteStatus = PersistentSubscriptionDeleteStatus;
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
const SliceReadStatus = {
|
const SliceReadStatus = Object.freeze({
|
||||||
Success: 'success',
|
Success: 'success',
|
||||||
StreamNotFound: 'streamNotFound',
|
StreamNotFound: 'streamNotFound',
|
||||||
StreamDeleted: 'streamDeleted'
|
StreamDeleted: 'streamDeleted'
|
||||||
};
|
});
|
||||||
Object.freeze(SliceReadStatus);
|
|
||||||
|
|
||||||
module.exports = SliceReadStatus;
|
module.exports = SliceReadStatus;
|
@ -1,4 +1,4 @@
|
|||||||
const SubscriptionDropReason = {
|
const SubscriptionDropReason = Object.freeze({
|
||||||
AccessDenied: 'accessDenied',
|
AccessDenied: 'accessDenied',
|
||||||
CatchUpError: 'catchUpError',
|
CatchUpError: 'catchUpError',
|
||||||
ConnectionClosed: 'connectionClosed',
|
ConnectionClosed: 'connectionClosed',
|
||||||
@ -11,6 +11,6 @@ const SubscriptionDropReason = {
|
|||||||
SubscribingError: 'subscribingError',
|
SubscribingError: 'subscribingError',
|
||||||
UserInitiated: 'userInitiated',
|
UserInitiated: 'userInitiated',
|
||||||
Unknown: 'unknown'
|
Unknown: 'unknown'
|
||||||
};
|
});
|
||||||
|
|
||||||
module.exports = SubscriptionDropReason;
|
module.exports = SubscriptionDropReason;
|
@ -1,8 +1,7 @@
|
|||||||
const SystemConsumerStrategies = {
|
const SystemConsumerStrategies = Object.freeze({
|
||||||
DispatchToSingle: 'DispatchToSingle',
|
DispatchToSingle: 'DispatchToSingle',
|
||||||
RoundRobin: 'RoundRobin',
|
RoundRobin: 'RoundRobin',
|
||||||
Pinned: 'Pinned'
|
Pinned: 'Pinned'
|
||||||
};
|
});
|
||||||
Object.freeze(SystemConsumerStrategies);
|
|
||||||
|
|
||||||
module.exports = SystemConsumerStrategies;
|
module.exports = SystemConsumerStrategies;
|
@ -1,9 +1,9 @@
|
|||||||
var InspectionDecision = {
|
var InspectionDecision = Object.freeze({
|
||||||
DoNothing: 'doNothing',
|
DoNothing: 'doNothing',
|
||||||
EndOperation: 'endOperation',
|
EndOperation: 'endOperation',
|
||||||
Retry: 'retry',
|
Retry: 'retry',
|
||||||
Reconnect: 'reconnect',
|
Reconnect: 'reconnect',
|
||||||
Subscribed: 'subscribed'
|
Subscribed: 'subscribed'
|
||||||
};
|
});
|
||||||
|
|
||||||
module.exports = InspectionDecision;
|
module.exports = InspectionDecision;
|
@ -1,9 +1,7 @@
|
|||||||
var ClientMessage = require('../messages/clientMessage');
|
var ClientMessage = require('../messages/clientMessage');
|
||||||
var SliceReadStatus = require('../sliceReadStatus');
|
var SliceReadStatus = require('../sliceReadStatus');
|
||||||
|
|
||||||
module.exports = {};
|
exports.convert = function(code) {
|
||||||
|
|
||||||
module.exports.convert = function(code) {
|
|
||||||
switch(code) {
|
switch(code) {
|
||||||
case ClientMessage.ReadStreamEventsCompleted.ReadStreamResult.Success:
|
case ClientMessage.ReadStreamEventsCompleted.ReadStreamResult.Success:
|
||||||
return SliceReadStatus.Success;
|
return SliceReadStatus.Success;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const TcpCommand = {
|
const TcpCommand = Object.freeze({
|
||||||
HeartbeatRequestCommand: 0x01,
|
HeartbeatRequestCommand: 0x01,
|
||||||
HeartbeatResponseCommand: 0x02,
|
HeartbeatResponseCommand: 0x02,
|
||||||
|
|
||||||
@ -73,16 +73,18 @@ const TcpCommand = {
|
|||||||
Authenticated: 0xF3,
|
Authenticated: 0xF3,
|
||||||
NotAuthenticated: 0xF4,
|
NotAuthenticated: 0xF4,
|
||||||
IdentifyClient: 0xF5,
|
IdentifyClient: 0xF5,
|
||||||
ClientIdentified: 0xF6
|
ClientIdentified: 0xF6,
|
||||||
};
|
|
||||||
|
getName: function(v) {
|
||||||
|
return _reverseLookup[v];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var _reverseLookup = {};
|
var _reverseLookup = {};
|
||||||
for(var n in TcpCommand) {
|
for(var n in TcpCommand) {
|
||||||
|
if (n === 'getName') continue;
|
||||||
var v = TcpCommand[n];
|
var v = TcpCommand[n];
|
||||||
_reverseLookup[v] = n;
|
_reverseLookup[v] = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TcpCommand;
|
module.exports = TcpCommand;
|
||||||
module.exports.getName = function(v) {
|
|
||||||
return _reverseLookup[v];
|
|
||||||
};
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const TcpFlags = {
|
const TcpFlags = Object.freeze({
|
||||||
None: 0x0,
|
None: 0x0,
|
||||||
Authenticated: 0x01
|
Authenticated: 0x01
|
||||||
};
|
});
|
||||||
|
|
||||||
module.exports = TcpFlags;
|
module.exports = TcpFlags;
|
||||||
|
@ -19,8 +19,7 @@ function TcpPackage(command, flags, correlationId, login, password, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TcpPackage.fromBufferSegment = function(data) {
|
TcpPackage.fromBufferSegment = function(data) {
|
||||||
if (data.length < MandatorySize)
|
if (data.length < MandatorySize) throw new Error("ArraySegment too short, length: " + data.length);
|
||||||
throw new Error("ArraySegment too short, length: " + data.length);
|
|
||||||
|
|
||||||
var command = data.buffer[data.offset + CommandOffset];
|
var command = data.buffer[data.offset + CommandOffset];
|
||||||
var flags = data.buffer[data.offset + FlagsOffset];
|
var flags = data.buffer[data.offset + FlagsOffset];
|
||||||
@ -32,13 +31,15 @@ TcpPackage.fromBufferSegment = function(data) {
|
|||||||
if ((flags & TcpFlags.Authenticated) !== 0)
|
if ((flags & TcpFlags.Authenticated) !== 0)
|
||||||
{
|
{
|
||||||
var loginLen = data.buffer[data.offset + AuthOffset];
|
var loginLen = data.buffer[data.offset + AuthOffset];
|
||||||
if (AuthOffset + 1 + loginLen + 1 >= data.count)
|
if (AuthOffset + 1 + loginLen + 1 >= data.count) {
|
||||||
throw new Error("Login length is too big, it doesn't fit into TcpPackage.");
|
throw new Error("Login length is too big, it doesn't fit into TcpPackage.");
|
||||||
|
}
|
||||||
login = data.buffer.toString('utf8', data.offset + AuthOffset + 1, data.offset + AuthOffset + 1 + loginLen);
|
login = data.buffer.toString('utf8', data.offset + AuthOffset + 1, data.offset + AuthOffset + 1 + loginLen);
|
||||||
|
|
||||||
var passLen = data.buffer[data.offset + AuthOffset + 1 + loginLen];
|
var passLen = data.buffer[data.offset + AuthOffset + 1 + loginLen];
|
||||||
if (AuthOffset + 1 + loginLen + 1 + passLen > data.count)
|
if (AuthOffset + 1 + loginLen + 1 + passLen > data.count) {
|
||||||
throw new Error("Password length is too big, it doesn't fit into TcpPackage.");
|
throw new Error("Password length is too big, it doesn't fit into TcpPackage.");
|
||||||
|
}
|
||||||
headerSize += 1 + loginLen + 1 + passLen;
|
headerSize += 1 + loginLen + 1 + passLen;
|
||||||
pass = data.buffer.toString('utf8', data.offset + AuthOffset + 1 + loginLen + 1, data.offset + headerSize);
|
pass = data.buffer.toString('utf8', data.offset + AuthOffset + 1 + loginLen + 1, data.offset + headerSize);
|
||||||
}
|
}
|
||||||
@ -49,12 +50,12 @@ TcpPackage.fromBufferSegment = function(data) {
|
|||||||
|
|
||||||
TcpPackage.prototype.asBuffer = function() {
|
TcpPackage.prototype.asBuffer = function() {
|
||||||
if ((this.flags & TcpFlags.Authenticated) !== 0) {
|
if ((this.flags & TcpFlags.Authenticated) !== 0) {
|
||||||
var loginBytes = new Buffer(this.login);
|
var loginBytes = Buffer.from(this.login);
|
||||||
if (loginBytes.length > 255) throw new Error("Login serialized length should be less than 256 bytes.");
|
if (loginBytes.length > 255) throw new Error("Login serialized length should be less than 256 bytes.");
|
||||||
var passwordBytes = new Buffer(this.password);
|
var passwordBytes = Buffer.from(this.password);
|
||||||
if (passwordBytes.length > 255) throw new Error("Password serialized length should be less than 256 bytes.");
|
if (passwordBytes.length > 255) throw new Error("Password serialized length should be less than 256 bytes.");
|
||||||
|
|
||||||
var res = new Buffer(MandatorySize + 2 + loginBytes.length + passwordBytes.length + (this.data ? this.data.count : 0));
|
var res = Buffer.alloc(MandatorySize + 2 + loginBytes.length + passwordBytes.length + (this.data ? this.data.count : 0));
|
||||||
res[CommandOffset] = this.command;
|
res[CommandOffset] = this.command;
|
||||||
res[FlagsOffset] = this.flags;
|
res[FlagsOffset] = this.flags;
|
||||||
guidParse.parse(this.correlationId, res, CorrelationOffset);
|
guidParse.parse(this.correlationId, res, CorrelationOffset);
|
||||||
@ -64,17 +65,15 @@ TcpPackage.prototype.asBuffer = function() {
|
|||||||
res[AuthOffset + 1 + loginBytes.length] = passwordBytes.length;
|
res[AuthOffset + 1 + loginBytes.length] = passwordBytes.length;
|
||||||
passwordBytes.copy(res, AuthOffset + 2 + loginBytes.length);
|
passwordBytes.copy(res, AuthOffset + 2 + loginBytes.length);
|
||||||
|
|
||||||
if (this.data)
|
if (this.data) this.data.copyTo(res, res.length - this.data.count);
|
||||||
this.data.copyTo(res, res.length - this.data.count);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
var res = new Buffer(MandatorySize + (this.data ? this.data.count : 0));
|
var res = Buffer.alloc(MandatorySize + (this.data ? this.data.count : 0));
|
||||||
res[CommandOffset] = this.command;
|
res[CommandOffset] = this.command;
|
||||||
res[FlagsOffset] = this.flags;
|
res[FlagsOffset] = this.flags;
|
||||||
guidParse.parse(this.correlationId, res, CorrelationOffset);
|
guidParse.parse(this.correlationId, res, CorrelationOffset);
|
||||||
if (this.data)
|
if (this.data) this.data.copyTo(res, AuthOffset);
|
||||||
this.data.copyTo(res, AuthOffset);
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -31,10 +31,11 @@ LengthPrefixMessageFramer.prototype._parse = function(bytes) {
|
|||||||
++this._headerBytes;
|
++this._headerBytes;
|
||||||
if (this._headerBytes === HeaderLength)
|
if (this._headerBytes === HeaderLength)
|
||||||
{
|
{
|
||||||
if (this._packageLength <= 0 || this._packageLength > this._maxPackageSize)
|
if (this._packageLength <= 0 || this._packageLength > this._maxPackageSize) {
|
||||||
throw new Error(["Package size is out of bounds: ", this._packageLength, "(max: ", this._maxPackageSize, "."].join(''));
|
throw new Error(["Package size is out of bounds: ", this._packageLength, "(max: ", this._maxPackageSize, "."].join(''));
|
||||||
|
}
|
||||||
|
|
||||||
this._messageBuffer = new Buffer(this._packageLength);
|
this._messageBuffer = Buffer.alloc(this._packageLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -46,8 +47,9 @@ LengthPrefixMessageFramer.prototype._parse = function(bytes) {
|
|||||||
|
|
||||||
if (this._bufferIndex === this._packageLength)
|
if (this._bufferIndex === this._packageLength)
|
||||||
{
|
{
|
||||||
if (this._receivedHandler !== null)
|
if (this._receivedHandler !== null) {
|
||||||
this._receivedHandler(createBufferSegment(this._messageBuffer, 0, this._bufferIndex));
|
this._receivedHandler(createBufferSegment(this._messageBuffer, 0, this._bufferIndex));
|
||||||
|
}
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,7 +58,7 @@ LengthPrefixMessageFramer.prototype._parse = function(bytes) {
|
|||||||
|
|
||||||
LengthPrefixMessageFramer.prototype.frameData = function(data) {
|
LengthPrefixMessageFramer.prototype.frameData = function(data) {
|
||||||
var length = data.count;
|
var length = data.count;
|
||||||
var lengthBuffer = new Buffer(HeaderLength);
|
var lengthBuffer = Buffer.alloc(HeaderLength);
|
||||||
lengthBuffer.writeInt32LE(length, 0);
|
lengthBuffer.writeInt32LE(length, 0);
|
||||||
return [
|
return [
|
||||||
createBufferSegment(lengthBuffer, 0, HeaderLength),
|
createBufferSegment(lengthBuffer, 0, HeaderLength),
|
||||||
|
@ -35,16 +35,15 @@ TcpConnection.prototype._initSocket = function(socket) {
|
|||||||
this._localEndPoint = {host: socket.localAddress, port: socket.localPort};
|
this._localEndPoint = {host: socket.localAddress, port: socket.localPort};
|
||||||
this._remoteEndPoint.host = socket.remoteAddress;
|
this._remoteEndPoint.host = socket.remoteAddress;
|
||||||
|
|
||||||
this._socket.on('drain', this._trySend.bind(this));
|
|
||||||
this._socket.on('error', this._processError.bind(this));
|
this._socket.on('error', this._processError.bind(this));
|
||||||
|
this._socket.on('drain', this._trySend.bind(this));
|
||||||
this._socket.on('data', this._processReceive.bind(this));
|
this._socket.on('data', this._processReceive.bind(this));
|
||||||
|
this._socket.on('close', this._processClose.bind(this));
|
||||||
|
|
||||||
this._trySend();
|
this._trySend();
|
||||||
};
|
};
|
||||||
|
|
||||||
TcpConnection.prototype.enqueueSend = function(bufSegmentArray) {
|
TcpConnection.prototype.enqueueSend = function(bufSegmentArray) {
|
||||||
//console.log(bufSegmentArray);
|
|
||||||
|
|
||||||
for(var i = 0; i < bufSegmentArray.length; i++) {
|
for(var i = 0; i < bufSegmentArray.length; i++) {
|
||||||
var bufSegment = bufSegmentArray[i];
|
var bufSegment = bufSegmentArray[i];
|
||||||
this._sendQueue.push(bufSegment.toBuffer());
|
this._sendQueue.push(bufSegment.toBuffer());
|
||||||
@ -59,17 +58,14 @@ TcpConnection.prototype._trySend = function() {
|
|||||||
var buffers = [];
|
var buffers = [];
|
||||||
var bytes = 0;
|
var bytes = 0;
|
||||||
var sendPiece;
|
var sendPiece;
|
||||||
while(sendPiece = this._sendQueue.shift()) {
|
while((sendPiece = this._sendQueue.shift())) {
|
||||||
buffers.push(sendPiece);
|
buffers.push(sendPiece);
|
||||||
bytes += sendPiece.length;
|
bytes += sendPiece.length;
|
||||||
if (bytes > MaxSendPacketSize)
|
if (bytes > MaxSendPacketSize) break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var joinedBuffers = Buffer.concat(buffers, bytes);
|
var joinedBuffers = Buffer.concat(buffers, bytes);
|
||||||
if (!this._socket.write(joinedBuffers)) {
|
if (!this._socket.write(joinedBuffers)) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setImmediate(this._trySend.bind(this));
|
setImmediate(this._trySend.bind(this));
|
||||||
};
|
};
|
||||||
@ -78,6 +74,10 @@ TcpConnection.prototype._processError = function(err) {
|
|||||||
this._closeInternal(err, "Socket error");
|
this._closeInternal(err, "Socket error");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TcpConnection.prototype._processClose = function(had_error) {
|
||||||
|
this._closeInternal(had_error, "Socket closed");
|
||||||
|
};
|
||||||
|
|
||||||
TcpConnection.prototype._processReceive = function(buf) {
|
TcpConnection.prototype._processReceive = function(buf) {
|
||||||
if (buf.length === 0) {
|
if (buf.length === 0) {
|
||||||
//NotifyReceiveCompleted(0);
|
//NotifyReceiveCompleted(0);
|
||||||
@ -97,8 +97,7 @@ TcpConnection.prototype.receive = function(cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TcpConnection.prototype._tryDequeueReceivedData = function() {
|
TcpConnection.prototype._tryDequeueReceivedData = function() {
|
||||||
if (this._receiveCallback === null || this._receiveQueue.length === 0)
|
if (this._receiveCallback === null || this._receiveQueue.length === 0) return;
|
||||||
return;
|
|
||||||
|
|
||||||
var res = [];
|
var res = [];
|
||||||
while(this._receiveQueue.length > 0) {
|
while(this._receiveQueue.length > 0) {
|
||||||
@ -112,8 +111,9 @@ TcpConnection.prototype._tryDequeueReceivedData = function() {
|
|||||||
callback(this, res);
|
callback(this, res);
|
||||||
|
|
||||||
var bytes = 0;
|
var bytes = 0;
|
||||||
for(var i=0;i<res.length;i++)
|
for(var i=0;i<res.length;i++) {
|
||||||
bytes += res[i].count;
|
bytes += res[i].count;
|
||||||
|
}
|
||||||
|
|
||||||
//this._pendingReceivedBytes -= bytes;
|
//this._pendingReceivedBytes -= bytes;
|
||||||
};
|
};
|
||||||
@ -132,8 +132,7 @@ TcpConnection.prototype._closeInternal = function(err, reason) {
|
|||||||
this._socket = null;
|
this._socket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._onConnectionClosed !== null)
|
if (this._onConnectionClosed !== null) this._onConnectionClosed(this, err);
|
||||||
this._onConnectionClosed(this, err);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TcpConnection.createConnectingConnection = function(
|
TcpConnection.createConnectingConnection = function(
|
||||||
@ -144,20 +143,31 @@ TcpConnection.createConnectingConnection = function(
|
|||||||
var provider = ssl ? tls : net;
|
var provider = ssl ? tls : net;
|
||||||
var options = {
|
var options = {
|
||||||
servername: targetHost,
|
servername: targetHost,
|
||||||
rejectUnauthorized: validateServer
|
rejectUnauthorized: validateServer,
|
||||||
|
port: remoteEndPoint.port,
|
||||||
|
host: remoteEndPoint.host,
|
||||||
|
timeout: connectionTimeout
|
||||||
};
|
};
|
||||||
var socket = provider.connect(remoteEndPoint.port, remoteEndPoint.host, options);
|
var socket = provider.connect(options, function() {
|
||||||
function onError(err) {
|
|
||||||
if (onConnectionFailed)
|
|
||||||
onConnectionFailed(connection, err);
|
|
||||||
}
|
|
||||||
socket.once('error', onError);
|
|
||||||
socket.on('connect', function() {
|
|
||||||
socket.removeListener('error', onError);
|
socket.removeListener('error', onError);
|
||||||
connection._initSocket(socket);
|
connection._initSocket(socket);
|
||||||
if (onConnectionEstablished)
|
if (onConnectionEstablished) onConnectionEstablished(connection);
|
||||||
onConnectionEstablished(connection);
|
|
||||||
});
|
});
|
||||||
|
var timer = setTimeout(function(){
|
||||||
|
log.error('TcpConnection: timeout when connecting to %j in %d ms', remoteEndPoint, connectionTimeout);
|
||||||
|
connection.close();
|
||||||
|
if (onConnectionFailed) onConnectionFailed(connection, new Error('Connection failed'));
|
||||||
|
}, connectionTimeout)
|
||||||
|
socket.once('error', onError);
|
||||||
|
function onError(err) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
if (onConnectionFailed) onConnectionFailed(connection, err);
|
||||||
|
}
|
||||||
|
socket.once('connect', onConnect);
|
||||||
|
function onConnect() {
|
||||||
|
log.info('TcpConnection: successfully connected to %j', remoteEndPoint);
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
return connection;
|
return connection;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
|
||||||
|
|
||||||
var LengthPrefixMessageFramer = require('./lengthPrefixMessageFramer');
|
var LengthPrefixMessageFramer = require('./lengthPrefixMessageFramer');
|
||||||
var TcpConnection = require('./tcpConnection');
|
var TcpConnection = require('./tcpConnection');
|
||||||
@ -55,13 +54,11 @@ function TcpPackageConnection(
|
|||||||
log.debug("TcpPackageConnection: connection to [%j, L%j, %s] failed. Error: %s.", conn.remoteEndPoint, conn.localEndPoint, connectionId, error);
|
log.debug("TcpPackageConnection: connection to [%j, L%j, %s] failed. Error: %s.", conn.remoteEndPoint, conn.localEndPoint, connectionId, error);
|
||||||
connectionClosed(self, error);
|
connectionClosed(self, error);
|
||||||
},
|
},
|
||||||
function (conn, had_error) {
|
function (conn, error) {
|
||||||
var error;
|
if (error === true) error = new Error('transmission error.');
|
||||||
if (had_error)
|
|
||||||
error = new Error('transmission error.');
|
|
||||||
|
|
||||||
log.debug("TcpPackageConnection: connection [%j, L%j, %s] was closed %s", conn.remoteEndPoint, conn.localEndPoint,
|
log.debug("TcpPackageConnection: connection [%j, L%j, %s] was closed %s", conn.remoteEndPoint, conn.localEndPoint,
|
||||||
connectionId, had_error ? "with error: " + error + "." : "cleanly.");
|
connectionId, error ? "with error: " + error.stack : "cleanly.");
|
||||||
|
|
||||||
connectionClosed(self, error);
|
connectionClosed(self, error);
|
||||||
});
|
});
|
||||||
@ -120,27 +117,23 @@ TcpPackageConnection.prototype._incomingMessageArrived = function(data) {
|
|||||||
var message = util.format("TcpPackageConnection: [%j, L%j, %s] ERROR for %s. Connection will be closed.",
|
var message = util.format("TcpPackageConnection: [%j, L%j, %s] ERROR for %s. Connection will be closed.",
|
||||||
this.remoteEndPoint, this.localEndPoint, this._connectionId,
|
this.remoteEndPoint, this.localEndPoint, this._connectionId,
|
||||||
valid ? TcpCommand.getName(pkg.command) : "<invalid package>");
|
valid ? TcpCommand.getName(pkg.command) : "<invalid package>");
|
||||||
if (this._onError !== null)
|
if (this._onError !== null) this._onError(this, e);
|
||||||
this._onError(this, e);
|
|
||||||
this._log.debug(e, message);
|
this._log.debug(e, message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TcpPackageConnection.prototype.startReceiving = function() {
|
TcpPackageConnection.prototype.startReceiving = function() {
|
||||||
if (this._connection === null)
|
if (this._connection === null) throw new Error("Failed connection.");
|
||||||
throw new Error("Failed connection.");
|
|
||||||
this._connection.receive(this._onRawDataReceived.bind(this));
|
this._connection.receive(this._onRawDataReceived.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
TcpPackageConnection.prototype.enqueueSend = function(pkg) {
|
TcpPackageConnection.prototype.enqueueSend = function(pkg) {
|
||||||
if (this._connection === null)
|
if (this._connection === null) throw new Error("Failed connection.");
|
||||||
throw new Error("Failed connection.");
|
|
||||||
this._connection.enqueueSend(this._framer.frameData(pkg.asBufferSegment()));
|
this._connection.enqueueSend(this._framer.frameData(pkg.asBufferSegment()));
|
||||||
};
|
};
|
||||||
|
|
||||||
TcpPackageConnection.prototype.close = function(reason) {
|
TcpPackageConnection.prototype.close = function(reason) {
|
||||||
if (this._connection === null)
|
if (this._connection === null) throw new Error("Failed connection.");
|
||||||
throw new Error("Failed connection.");
|
|
||||||
this._connection.close(reason);
|
this._connection.close(reason);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
17
src/utils/modules.js
Normal file
17
src/utils/modules.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
exports.uncache = function uncache(id) {
|
||||||
|
var mod = require.cache[id];
|
||||||
|
if (!mod) return;
|
||||||
|
delete require.cache[id];
|
||||||
|
if (mod.children)
|
||||||
|
for(var i = 0; i < mod.children.length; i++)
|
||||||
|
uncache(mod.children[i].id);
|
||||||
|
return mod;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.recache = function recache(mod) {
|
||||||
|
if (require.cache[mod.id] === mod) return;
|
||||||
|
require.cache[mod.id] = mod;
|
||||||
|
if (mod.children)
|
||||||
|
for(var i = 0; i < mod.children.length; i++)
|
||||||
|
recache(mod.children[i]);
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
var Long = require('long');
|
var Long = require('long');
|
||||||
|
|
||||||
var ANY_VERSION = Long.fromNumber(client.expectedVersion.any);
|
var ANY_VERSION = Long.fromNumber(client.expectedVersion.any);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
var client = require('../../src/client');
|
var client = require('../../lib/dist');
|
||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
|
|
||||||
var settings = {
|
var settings = {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
var client = require('../../src/client');
|
var client = require('../../lib/dist');
|
||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
|
|
||||||
var settings = {
|
var settings = {
|
||||||
|
@ -1,42 +1,112 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../../src/client');
|
var Long = require('long');
|
||||||
|
var client = require('../../lib/dist');
|
||||||
var FileLogger = require('../../src/common/log/fileLogger');
|
var FileLogger = require('../../src/common/log/fileLogger');
|
||||||
var NoopLogger = require('../../src/common/log/noopLogger');
|
var NoopLogger = require('../../src/common/log/noopLogger');
|
||||||
|
|
||||||
|
// Make sure we mess with protobufjs setup for bug #91
|
||||||
|
var protobufJS = require('protobufjs');
|
||||||
|
protobufJS.util.Long = undefined;
|
||||||
|
protobufJS.configure();
|
||||||
|
|
||||||
var settings = {
|
var settings = {
|
||||||
log: new NoopLogger()
|
log: new NoopLogger(),
|
||||||
};
|
};
|
||||||
if (process.env.TESTS_VERBOSE_LOGGING === '1') {
|
if (process.env.TESTS_VERBOSE_LOGGING === '1') {
|
||||||
settings.verboseLogging = true;
|
settings.verboseLogging = true;
|
||||||
settings.log = new FileLogger('test-verbose.log');
|
settings.log = new FileLogger('test-verbose.log');
|
||||||
}
|
}
|
||||||
|
|
||||||
var tcpEndPoint = {host: 'localhost', port: 1113};
|
function setUpWithGossipSeeds(cb) {
|
||||||
|
var gossipSeeds = [
|
||||||
function setUp(cb) {
|
new client.GossipSeed({ host: process.env.EVENTSTORE_HOST_1 || '192.168.33.10', port: 2113 }),
|
||||||
|
new client.GossipSeed({ host: process.env.EVENTSTORE_HOST_2 || '192.168.33.11', port: 2113 }),
|
||||||
|
new client.GossipSeed({ host: process.env.EVENTSTORE_HOST_3 || '192.168.33.12', port: 2113 }),
|
||||||
|
];
|
||||||
this.log = settings.log;
|
this.log = settings.log;
|
||||||
this.testStreamName = 'test-' + uuid.v4();
|
this.testStreamName = 'test-' + uuid.v4();
|
||||||
var connected = false;
|
var connected = false;
|
||||||
this.conn = client.EventStoreConnection.create(settings, tcpEndPoint);
|
this.conn = client.createConnection(settings, gossipSeeds);
|
||||||
this.conn.connect()
|
this.conn
|
||||||
|
.connect()
|
||||||
.then(function () {
|
.then(function () {
|
||||||
//Doesn't mean anything, connection is just initiated
|
//Doesn't mean anything, connection is just initiated
|
||||||
settings.log.debug("Connection to %j initialized.", tcpEndPoint);
|
settings.log.debug('Connection to %j initialized.', gossipSeeds);
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
settings.log.error(err, "Initializing connection to %j failed.", tcpEndPoint);
|
settings.log.error(err, 'Initializing connection to %j failed.', gossipSeeds);
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
this.conn.on('closed', function (reason) {
|
this.conn.on('closed', function (reason) {
|
||||||
if (connected) return;
|
if (connected) return;
|
||||||
var error = new Error("Connection failed: " + reason);
|
var error = new Error('Connection failed: ' + reason);
|
||||||
settings.log.error(error, "Connection to %j failed.", tcpEndPoint);
|
settings.log.error(error, 'Connection to %j failed.', gossipSeeds);
|
||||||
cb(error);
|
cb(error);
|
||||||
});
|
});
|
||||||
this.conn.on('connected', function (tcpEndPoint) {
|
this.conn.on('connected', function (tcpEndPoint) {
|
||||||
if (connected) return;
|
if (connected) return;
|
||||||
settings.log.debug("Connected to %j.", tcpEndPoint);
|
settings.log.debug('Connected to %j.', tcpEndPoint);
|
||||||
|
connected = true;
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUpWithDns(cb) {
|
||||||
|
var clusterDns = 'discover://eventstore.local:2113';
|
||||||
|
this.log = settings.log;
|
||||||
|
this.testStreamName = 'test-' + uuid.v4();
|
||||||
|
var connected = false;
|
||||||
|
this.conn = client.createConnection(settings, clusterDns);
|
||||||
|
this.conn
|
||||||
|
.connect()
|
||||||
|
.then(function () {
|
||||||
|
//Doesn't mean anything, connection is just initiated
|
||||||
|
settings.log.debug('Connection to %j initialized.', clusterDns);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
settings.log.error(err, 'Initializing connection to %j failed.', clusterDns);
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
this.conn.on('closed', function (reason) {
|
||||||
|
if (connected) return;
|
||||||
|
var error = new Error('Connection failed: ' + reason);
|
||||||
|
settings.log.error(error, 'Connection to %j failed.', clusterDns);
|
||||||
|
cb(error);
|
||||||
|
});
|
||||||
|
this.conn.on('connected', function (tcpEndPoint) {
|
||||||
|
if (connected) return;
|
||||||
|
settings.log.debug('Connected to %j.', tcpEndPoint);
|
||||||
|
connected = true;
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUpWithTcpEndpoint(cb) {
|
||||||
|
var tcpEndPoint = { host: process.env.EVENTSTORE_HOST || 'localhost', port: 1113 };
|
||||||
|
this.log = settings.log;
|
||||||
|
this.testStreamName = 'test-' + uuid.v4();
|
||||||
|
var connected = false;
|
||||||
|
this.conn = client.EventStoreConnection.create(settings, tcpEndPoint);
|
||||||
|
this.conn
|
||||||
|
.connect()
|
||||||
|
.then(function () {
|
||||||
|
//Doesn't mean anything, connection is just initiated
|
||||||
|
settings.log.debug('Connection to %j initialized.', tcpEndPoint);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
settings.log.error(err, 'Initializing connection to %j failed.', tcpEndPoint);
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
this.conn.on('closed', function (reason) {
|
||||||
|
if (connected) return;
|
||||||
|
var error = new Error('Connection failed: ' + reason);
|
||||||
|
settings.log.error(error, 'Connection to %j failed.', tcpEndPoint);
|
||||||
|
cb(error);
|
||||||
|
});
|
||||||
|
this.conn.on('connected', function (tcpEndPoint) {
|
||||||
|
if (connected) return;
|
||||||
|
settings.log.debug('Connected to %j.', tcpEndPoint);
|
||||||
connected = true;
|
connected = true;
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
@ -44,7 +114,7 @@ function setUp(cb) {
|
|||||||
|
|
||||||
function tearDown(cb) {
|
function tearDown(cb) {
|
||||||
this.conn.close();
|
this.conn.close();
|
||||||
this.conn.on('closed', function() {
|
this.conn.on('closed', function () {
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
this.conn = null;
|
this.conn = null;
|
||||||
@ -52,9 +122,8 @@ function tearDown(cb) {
|
|||||||
|
|
||||||
function areEqual(name, actual, expected) {
|
function areEqual(name, actual, expected) {
|
||||||
if (typeof expected !== 'object' || expected === null)
|
if (typeof expected !== 'object' || expected === null)
|
||||||
this.strictEqual(actual, expected, util.format("Failed %s === %s, got %s.", name, expected, actual));
|
this.strictEqual(actual, expected, util.format('Failed %s === %s, got %s.', name, expected, actual));
|
||||||
else
|
else this.deepEqual(actual, expected, util.format('Failed %s deepEqual %j, got %j.', name, expected, actual));
|
||||||
this.deepEqual(actual, expected, util.format("Failed %s deepEqual %j, got %j.", name, expected, actual));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fail(reason) {
|
function fail(reason) {
|
||||||
@ -63,49 +132,75 @@ function fail(reason) {
|
|||||||
|
|
||||||
function eventEqualEventData(name, resolvedEvent, eventData) {
|
function eventEqualEventData(name, resolvedEvent, eventData) {
|
||||||
var ev = resolvedEvent.originalEvent;
|
var ev = resolvedEvent.originalEvent;
|
||||||
this.ok(ev !== null, util.format("Failed %s !== null.", name + ".originalEvent"));
|
this.ok(ev !== null, util.format('Failed %s !== null.', name + '.originalEvent'));
|
||||||
if (ev === null) return;
|
if (ev === null) return;
|
||||||
this.areEqual(name + ".originalEvent.eventId", ev.eventId, eventData.eventId);
|
this.areEqual(name + '.originalEvent.eventId', ev.eventId, eventData.eventId);
|
||||||
this.areEqual(name + ".originalEvent.eventType", ev.eventType, eventData.type);
|
this.areEqual(name + '.originalEvent.eventType', ev.eventType, eventData.type);
|
||||||
this.ok(Buffer.compare(ev.data, eventData.data) === 0, name + ".originalEvent.data is not equal to original data.");
|
this.ok(Buffer.compare(ev.data, eventData.data) === 0, name + '.originalEvent.data is not equal to original data.');
|
||||||
this.ok(Buffer.compare(ev.metadata, eventData.metadata) === 0, name + ".originalEvent.metadata is not equal to original metadata.");
|
this.ok(
|
||||||
|
Buffer.compare(ev.metadata, eventData.metadata) === 0,
|
||||||
|
name + '.originalEvent.metadata is not equal to original metadata.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testRecordedEvent(name, event) {
|
||||||
|
this.ok(Long.isLong(event.eventNumber), name + '.eventNumber is not a Long');
|
||||||
|
this.ok(event.created instanceof Date, name + '.created is not a Date');
|
||||||
|
this.ok(typeof event.createdEpoch === 'number', name + '.createdEpoch is not a number');
|
||||||
}
|
}
|
||||||
|
|
||||||
function testLiveEvent(name, event, evNumber) {
|
function testLiveEvent(name, event, evNumber) {
|
||||||
this.ok(event.event, name + ".event not defined (or null)");
|
this.ok(event.event, name + '.event not defined (or null)');
|
||||||
this.ok(event.originalEvent, name + ".originalEvent not defined (or null)");
|
this.ok(event.originalEvent, name + '.originalEvent not defined (or null)');
|
||||||
this.ok(event.isResolved === false, name + ".isResolved should be true");
|
this.ok(event.isResolved === false, name + '.isResolved should be true');
|
||||||
this.ok(event.originalPosition instanceof client.Position, name + ".originalPosition is not an instance of Position");
|
this.ok(event.originalPosition instanceof client.Position, name + '.originalPosition is not an instance of Position');
|
||||||
this.ok(event.originalStreamId, name + ".originalStreamId not defined (or null)");
|
this.ok(event.originalStreamId, name + '.originalStreamId not defined (or null)');
|
||||||
|
this.ok(Long.isLong(event.originalEventNumber), name + '.originalEventNumber is not a Long');
|
||||||
if (typeof evNumber === 'number') {
|
if (typeof evNumber === 'number') {
|
||||||
this.ok(event.originalEventNumber.toNumber() === evNumber, name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber);
|
this.ok(
|
||||||
} else {
|
event.originalEventNumber.toNumber() === evNumber,
|
||||||
this.ok(typeof event.originalEventNumber === 'number', name + ".originalEventNumber is not a number");
|
name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
testRecordedEvent.call(this, name + '.event', event.event);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testReadEvent(name, event, evNumber) {
|
function testReadEvent(name, event, evNumber) {
|
||||||
this.ok(event.event, name + ".event not defined (or null)");
|
this.ok(event.event, name + '.event not defined (or null)');
|
||||||
this.ok(event.originalEvent, name + ".originalEvent not defined (or null)");
|
this.ok(event.originalEvent, name + '.originalEvent not defined (or null)');
|
||||||
this.ok(event.isResolved === false, name + ".isResolved should be true");
|
this.ok(event.isResolved === false, name + '.isResolved should be true');
|
||||||
this.ok(event.originalPosition === null, name + ".originalPosition is not null");
|
this.ok(event.originalPosition === null, name + '.originalPosition is not null');
|
||||||
this.ok(event.originalStreamId, name + ".originalStreamId not defined (or null)");
|
this.ok(event.originalStreamId, name + '.originalStreamId not defined (or null)');
|
||||||
|
this.ok(Long.isLong(event.originalEventNumber), name + '.originalEventNumber is not a Long');
|
||||||
if (typeof evNumber === 'number') {
|
if (typeof evNumber === 'number') {
|
||||||
this.ok(event.originalEventNumber.toNumber() === evNumber, name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber);
|
this.ok(
|
||||||
} else {
|
event.originalEventNumber.toNumber() === evNumber,
|
||||||
this.ok(typeof event.originalEventNumber === 'number', name + ".originalEventNumber is not a number");
|
name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
testRecordedEvent.call(this, name + '.event', event.event);
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = {
|
var _ = {
|
||||||
'setUp': setUp,
|
tearDown: tearDown,
|
||||||
'tearDown': tearDown
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
switch (process.env.EVENTSTORE_CONNECTION_TYPE) {
|
||||||
|
case 'gossip':
|
||||||
|
_.setUp = setUpWithGossipSeeds;
|
||||||
|
break;
|
||||||
|
case 'dns':
|
||||||
|
_.setUp = setUpWithDns;
|
||||||
|
break;
|
||||||
|
case 'tcp':
|
||||||
|
default:
|
||||||
|
_.setUp = setUpWithTcpEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
function wrap(name, testFunc) {
|
function wrap(name, testFunc) {
|
||||||
var base = _[name];
|
var base = _[name];
|
||||||
if (base === undefined) {
|
if (base === undefined) {
|
||||||
return function(test) {
|
return function (test) {
|
||||||
settings.log.debug('--- %s ---', name);
|
settings.log.debug('--- %s ---', name);
|
||||||
test.areEqual = areEqual.bind(test);
|
test.areEqual = areEqual.bind(test);
|
||||||
test.fail = fail.bind(test);
|
test.fail = fail.bind(test);
|
||||||
@ -113,36 +208,48 @@ function wrap(name, testFunc) {
|
|||||||
test.testLiveEvent = testLiveEvent.bind(test);
|
test.testLiveEvent = testLiveEvent.bind(test);
|
||||||
test.testReadEvent = testReadEvent.bind(test);
|
test.testReadEvent = testReadEvent.bind(test);
|
||||||
return testFunc.call(this, test);
|
return testFunc.call(this, test);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
return function (cb) {
|
||||||
return function(cb) {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
base.call(this, function(err) {
|
base.call(this, function (err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
return testFunc.call(self, cb);
|
return testFunc.call(self, cb);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.init = function(testSuite, addSetUpTearDownIfNotPresent) {
|
module.exports.init = function (testSuite, addSetUpTearDownIfNotPresent) {
|
||||||
var thisObj = {};
|
var thisObj = {};
|
||||||
if (addSetUpTearDownIfNotPresent === undefined) addSetUpTearDownIfNotPresent = true;
|
if (addSetUpTearDownIfNotPresent === undefined) addSetUpTearDownIfNotPresent = true;
|
||||||
for(var k in testSuite) {
|
for (var k in testSuite) {
|
||||||
if (testSuite.hasOwnProperty(k)) {
|
if (testSuite.hasOwnProperty(k)) {
|
||||||
testSuite[k] = wrap(k, testSuite[k]).bind(thisObj);
|
testSuite[k] = wrap(k, testSuite[k]).bind(thisObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!addSetUpTearDownIfNotPresent) return;
|
if (!addSetUpTearDownIfNotPresent) return;
|
||||||
if (!testSuite.hasOwnProperty('setUp')) testSuite['setUp'] = setUp.bind(thisObj);
|
if (!testSuite.hasOwnProperty('setUp')) {
|
||||||
|
switch (process.env.EVENTSTORE_CONNECTION_TYPE) {
|
||||||
|
case 'gossip':
|
||||||
|
testSuite['setUp'] = setUpWithGossipSeeds.bind(thisObj);
|
||||||
|
break;
|
||||||
|
case 'dns':
|
||||||
|
testSuite['setUp'] = setUpWithDns.bind(thisObj);
|
||||||
|
break;
|
||||||
|
case 'tcp':
|
||||||
|
default:
|
||||||
|
testSuite['setUp'] = setUpWithTcpEndpoint.bind(thisObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!testSuite.hasOwnProperty('tearDown')) testSuite['tearDown'] = tearDown.bind(thisObj);
|
if (!testSuite.hasOwnProperty('tearDown')) testSuite['tearDown'] = tearDown.bind(thisObj);
|
||||||
};
|
};
|
||||||
module.exports.settings = function(settingsOverride) {
|
module.exports.settings = function (settingsOverride) {
|
||||||
var obj = {};
|
var obj = {};
|
||||||
for(var prop in settings) {
|
for (var prop in settings) {
|
||||||
obj[prop] = settings[prop];
|
obj[prop] = settings[prop];
|
||||||
}
|
}
|
||||||
if (!settingsOverride) return obj;
|
if (!settingsOverride) return obj;
|
||||||
for(var prop in settingsOverride) {
|
for (var prop in settingsOverride) {
|
||||||
obj[prop] = settingsOverride[prop];
|
obj[prop] = settingsOverride[prop];
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
|
@ -1,60 +1,21 @@
|
|||||||
var client = require('../src/client.js');
|
var client = require('../lib/dist');
|
||||||
var GossipSeed = require('../src/gossipSeed');
|
var GossipSeed = require('../src/gossipSeed');
|
||||||
|
|
||||||
var testBase = require('./common/base_test');
|
var testBase = require('./common/base_test');
|
||||||
|
|
||||||
module.exports = {
|
var withSsl = !!process.env.NODE_ESC_WITH_SSL;
|
||||||
'Connect To Endpoint Happy Path': function(test) {
|
const evenstStoreType = process.env.EVENTSTORE_CONNECTION_TYPE;
|
||||||
test.expect(1);
|
|
||||||
var tcpEndpoint = {host: 'localhost', port: 1113};
|
|
||||||
var conn = client.EventStoreConnection.create(testBase.settings(), tcpEndpoint);
|
|
||||||
conn.connect()
|
|
||||||
.catch(function(err) {
|
|
||||||
test.done(err);
|
|
||||||
});
|
|
||||||
conn.on('connected', function(endPoint){
|
|
||||||
test.areEqual("connected endPoint", endPoint, tcpEndpoint);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
conn.on('error', done);
|
|
||||||
|
|
||||||
function done(err) {
|
module.exports = {}
|
||||||
conn.close();
|
|
||||||
if (err) return test.done(err);
|
switch(evenstStoreType){
|
||||||
test.done();
|
case 'gossip':
|
||||||
}
|
module.exports['Connect to Cluster using gossip seeds'] = function (test) {
|
||||||
},
|
|
||||||
'Connect To Endpoint That Doesn\'t Exist': function(test) {
|
|
||||||
test.expect(1);
|
|
||||||
var tcpEndpoint = {host: 'localhost', port: 11112};
|
|
||||||
var conn = client.EventStoreConnection.create(testBase.settings({maxReconnections:1}), tcpEndpoint);
|
|
||||||
conn.connect()
|
|
||||||
.catch(function (err) {
|
|
||||||
test.done(err);
|
|
||||||
});
|
|
||||||
conn.on('connected', function () {
|
|
||||||
test.ok(false, "Should not be able to connect.");
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
conn.on('error', function (err) {
|
|
||||||
test.done(err);
|
|
||||||
});
|
|
||||||
conn.on('closed', function(reason) {
|
|
||||||
test.ok(reason.indexOf("Reconnection limit reached") === 0, "Wrong expected reason.");
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
'Create a connection with tcp://host:port string': function(test) {
|
|
||||||
var conn = client.createConnection({}, 'tcp://localhost:1113');
|
|
||||||
conn.close();
|
|
||||||
test.done();
|
|
||||||
}/*,
|
|
||||||
'Connect to Cluster using gossip seeds': function (test) {
|
|
||||||
test.expect(1);
|
test.expect(1);
|
||||||
var gossipSeeds = [
|
var gossipSeeds = [
|
||||||
new GossipSeed({host: 'localhost', port: 1113}),
|
new GossipSeed({host: process.env.EVENTSTORE_HOST_1 || '192.168.33.10', port: 2113}),
|
||||||
new GossipSeed({host: 'localhost', port: 2113}),
|
new GossipSeed({host: process.env.EVENTSTORE_HOST_2 || '192.168.33.11', port: 2113}),
|
||||||
new GossipSeed({host: 'localhost', port: 3113})
|
new GossipSeed({host: process.env.EVENTSTORE_HOST_3 || '192.168.33.12', port: 2113})
|
||||||
];
|
];
|
||||||
var conn = client.EventStoreConnection.create(testBase.settings(), gossipSeeds);
|
var conn = client.EventStoreConnection.create(testBase.settings(), gossipSeeds);
|
||||||
conn.connect()
|
conn.connect()
|
||||||
@ -62,7 +23,7 @@ module.exports = {
|
|||||||
test.done(err);
|
test.done(err);
|
||||||
});
|
});
|
||||||
conn.on('connected', function(endPoint){
|
conn.on('connected', function(endPoint){
|
||||||
test.ok(endPoint, "no endpoint");
|
test.ok(endPoint, 'no endpoint');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
conn.on('error', done);
|
conn.on('error', done);
|
||||||
@ -72,13 +33,132 @@ module.exports = {
|
|||||||
if (err) return test.done(err);
|
if (err) return test.done(err);
|
||||||
test.done();
|
test.done();
|
||||||
}
|
}
|
||||||
}*/,
|
};
|
||||||
'Connect to secure tcp endpoint': function(test) {
|
|
||||||
|
module.exports['Connect To Cluster with bad gossip seeds'] = function (test) {
|
||||||
|
test.expect(3);
|
||||||
|
var gossipSeeds = [
|
||||||
|
new GossipSeed({host: '1.2.3.4', port: 1113}),
|
||||||
|
new GossipSeed({host: '2.3.4.5', port: 2113}),
|
||||||
|
new GossipSeed({host: '3.4.5.6', port: 3113})
|
||||||
|
];
|
||||||
|
var conn = client.EventStoreConnection.create(testBase.settings({maxDiscoverAttempts: 1}), gossipSeeds);
|
||||||
|
conn.connect()
|
||||||
|
.catch(function (err) {
|
||||||
|
test.ok(err.message.indexOf('Couldn\'t resolve target end point') === 0, 'Wrong expected reason.');
|
||||||
|
});
|
||||||
|
conn.on('connected', function () {
|
||||||
|
test.ok(false, 'Should not be able to connect.');
|
||||||
|
});
|
||||||
|
conn.on('error', function (err) {
|
||||||
|
test.ok(err.message.indexOf('Failed to discover candidate in 1 attempts') === 0, 'Wrong expected reason.');
|
||||||
|
});
|
||||||
|
conn.on('closed', function (reason) {
|
||||||
|
test.ok(reason.indexOf('Failed to resolve TCP end point to which to connect') === 0, 'Wrong expected reason.');
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'dns':
|
||||||
|
module.exports['Connect to Cluster using dns discover'] = function (test) {
|
||||||
|
test.expect(1);
|
||||||
|
var clusterDns = 'discover://eventstore.local:2113';
|
||||||
|
var conn = client.EventStoreConnection.create(testBase.settings(), clusterDns);
|
||||||
|
conn.connect()
|
||||||
|
.catch(function(err) {
|
||||||
|
test.done(err);
|
||||||
|
});
|
||||||
|
conn.on('connected', function(endPoint){
|
||||||
|
test.ok(endPoint, 'no endpoint');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
conn.on('error', done);
|
||||||
|
|
||||||
|
function done(err) {
|
||||||
|
conn.close();
|
||||||
|
if (err) return test.done(err);
|
||||||
|
test.done();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports['Connect To Cluster with bad dns discover'] = function (test) {
|
||||||
|
test.expect(3);
|
||||||
|
var clusterDns = 'discover://eventstore-bad.local:2113';
|
||||||
|
var conn = client.EventStoreConnection.create(testBase.settings({maxDiscoverAttempts: 1}), clusterDns);
|
||||||
|
conn.connect()
|
||||||
|
.catch(function (err) {
|
||||||
|
test.ok(err.message.indexOf('Couldn\'t resolve target end point') === 0, 'Wrong expected reason.');
|
||||||
|
});
|
||||||
|
conn.on('connected', function () {
|
||||||
|
test.ok(false, 'Should not be able to connect.');
|
||||||
|
});
|
||||||
|
conn.on('error', function (err) {
|
||||||
|
test.ok(err.message.indexOf('Failed to discover candidate in 1 attempts') === 0, 'Wrong expected reason.');
|
||||||
|
});
|
||||||
|
conn.on('closed', function (reason) {
|
||||||
|
test.ok(reason.indexOf('Failed to resolve TCP end point to which to connect') === 0, 'Wrong expected reason.');
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'tcp':
|
||||||
|
default:
|
||||||
|
module.exports['Connect To Endpoint Happy Path'] = function (test) {
|
||||||
|
test.expect(1);
|
||||||
|
var tcpEndpoint = {host: process.env.EVENTSTORE_HOST || 'localhost', port: 1113};
|
||||||
|
var conn = client.EventStoreConnection.create(testBase.settings(), tcpEndpoint);
|
||||||
|
conn.connect()
|
||||||
|
.catch(function (err) {
|
||||||
|
test.done(err);
|
||||||
|
});
|
||||||
|
conn.on('connected', function (endPoint) {
|
||||||
|
test.areEqual('connected endPoint', endPoint, tcpEndpoint);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
conn.on('error', done);
|
||||||
|
|
||||||
|
function done(err) {
|
||||||
|
conn.close();
|
||||||
|
if (err) return test.done(err);
|
||||||
|
test.done();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports['Connect To Endpoint That Doesn\'t Exist'] = function (test) {
|
||||||
|
test.expect(1);
|
||||||
|
var tcpEndpoint = {host: process.env.EVENTSTORE_HOST || 'localhost', port: 11112};
|
||||||
|
var conn = client.EventStoreConnection.create(testBase.settings({maxReconnections: 1}), tcpEndpoint);
|
||||||
|
conn.connect()
|
||||||
|
.catch(function (err) {
|
||||||
|
test.done(err);
|
||||||
|
});
|
||||||
|
conn.on('connected', function () {
|
||||||
|
test.ok(false, 'Should not be able to connect.');
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
conn.on('error', function (err) {
|
||||||
|
test.done(err);
|
||||||
|
});
|
||||||
|
conn.on('closed', function (reason) {
|
||||||
|
test.ok(reason.indexOf('Reconnection limit reached') === 0, 'Wrong expected reason.');
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports['Create a connection with tcp://host:port string'] = function (test) {
|
||||||
|
var conn = client.createConnection({}, `tcp://${process.env.EVENTSTORE_HOST || 'localhost'}:1113`);
|
||||||
|
conn.close();
|
||||||
|
test.done();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (withSsl) {
|
||||||
|
module.exports['Connect to secure tcp endpoint'] = function(test) {
|
||||||
var conn = client.createConnection({
|
var conn = client.createConnection({
|
||||||
useSslConnection: true,
|
useSslConnection: true,
|
||||||
targetHost: 'localhost',
|
targetHost: process.env.EVENTSTORE_HOST || 'localhost',
|
||||||
validateServer: false
|
validateServer: false
|
||||||
}, 'tcp://localhost:1115');
|
}, `tcp://${process.env.EVENTSTORE_HOST || 'localhost'}:1115`);
|
||||||
conn.on('error', function (err) {
|
conn.on('error', function (err) {
|
||||||
test.done(err);
|
test.done(err);
|
||||||
});
|
});
|
||||||
@ -90,6 +170,6 @@ module.exports = {
|
|||||||
test.done();
|
test.done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
testBase.init(module.exports, false);
|
testBase.init(module.exports, false);
|
@ -1,5 +1,5 @@
|
|||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
var Long = require('long');
|
var Long = require('long');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
98
test/fixtures/gossip.json
vendored
Normal file
98
test/fixtures/gossip.json
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"instanceId": "bb16857d-373d-4233-a175-89c917a72329",
|
||||||
|
"timeStamp": "2020-09-02T13:53:24.234898Z",
|
||||||
|
"state": "Slave",
|
||||||
|
"isAlive": false,
|
||||||
|
"internalTcpIp": "10.0.0.1",
|
||||||
|
"internalTcpPort": 1112,
|
||||||
|
"internalSecureTcpPort": 0,
|
||||||
|
"externalTcpIp": "10.0.0.1",
|
||||||
|
"externalTcpPort": 1113,
|
||||||
|
"externalSecureTcpPort": 0,
|
||||||
|
"internalHttpIp": "10.0.0.1",
|
||||||
|
"internalHttpPort": 2112,
|
||||||
|
"externalHttpIp": "10.0.0.1",
|
||||||
|
"externalHttpPort": 2113,
|
||||||
|
"lastCommitPosition": 648923382,
|
||||||
|
"writerCheckpoint": 648936339,
|
||||||
|
"chaserCheckpoint": 648936339,
|
||||||
|
"epochPosition": 551088596,
|
||||||
|
"epochNumber": 201,
|
||||||
|
"epochId": "d8f95f4b-167a-4487-9031-4d31a507e6d9",
|
||||||
|
"nodePriority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"instanceId": "b3c18dcd-6476-467a-b7b8-d6672b74e9c2",
|
||||||
|
"timeStamp": "2020-09-02T13:56:06.189428Z",
|
||||||
|
"state": "CatchingUp",
|
||||||
|
"isAlive": true,
|
||||||
|
"internalTcpIp": "10.0.0.2",
|
||||||
|
"internalTcpPort": 1112,
|
||||||
|
"internalSecureTcpPort": 0,
|
||||||
|
"externalTcpIp": "10.0.0.2",
|
||||||
|
"externalTcpPort": 1113,
|
||||||
|
"externalSecureTcpPort": 0,
|
||||||
|
"internalHttpIp": "10.0.0.2",
|
||||||
|
"internalHttpPort": 2112,
|
||||||
|
"externalHttpIp": "10.0.0.2",
|
||||||
|
"externalHttpPort": 2113,
|
||||||
|
"lastCommitPosition": -1,
|
||||||
|
"writerCheckpoint": 0,
|
||||||
|
"chaserCheckpoint": 0,
|
||||||
|
"epochPosition": -1,
|
||||||
|
"epochNumber": -1,
|
||||||
|
"epochId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"nodePriority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"instanceId": "e802a2b5-826c-4bd5-84d0-c9d1387fbf79",
|
||||||
|
"timeStamp": "2020-09-02T13:56:07.391534Z",
|
||||||
|
"state": "Master",
|
||||||
|
"isAlive": true,
|
||||||
|
"internalTcpIp": "10.0.0.3",
|
||||||
|
"internalTcpPort": 1112,
|
||||||
|
"internalSecureTcpPort": 0,
|
||||||
|
"externalTcpIp": "10.0.0.3",
|
||||||
|
"externalTcpPort": 1113,
|
||||||
|
"externalSecureTcpPort": 0,
|
||||||
|
"internalHttpIp": "10.0.0.3",
|
||||||
|
"internalHttpPort": 2112,
|
||||||
|
"externalHttpIp": "10.0.0.3",
|
||||||
|
"externalHttpPort": 2113,
|
||||||
|
"lastCommitPosition": 649007631,
|
||||||
|
"writerCheckpoint": 649024685,
|
||||||
|
"chaserCheckpoint": 649024685,
|
||||||
|
"epochPosition": 649023795,
|
||||||
|
"epochNumber": 202,
|
||||||
|
"epochId": "1f17695d-6558-4d8b-ba60-2ae273b11e09",
|
||||||
|
"nodePriority": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"instanceId": "24bb9031-5f21-436c-a7b5-c5f03a95e938",
|
||||||
|
"timeStamp": "2020-09-02T13:54:39.023053Z",
|
||||||
|
"state": "Slave",
|
||||||
|
"isAlive": false,
|
||||||
|
"internalTcpIp": "10.0.0.4",
|
||||||
|
"internalTcpPort": 1112,
|
||||||
|
"internalSecureTcpPort": 0,
|
||||||
|
"externalTcpIp": "10.0.0.4",
|
||||||
|
"externalTcpPort": 1113,
|
||||||
|
"externalSecureTcpPort": 0,
|
||||||
|
"internalHttpIp": "10.0.0.4",
|
||||||
|
"internalHttpPort": 2112,
|
||||||
|
"externalHttpIp": "10.0.0.4",
|
||||||
|
"externalHttpPort": 2113,
|
||||||
|
"lastCommitPosition": 649007631,
|
||||||
|
"writerCheckpoint": 649023795,
|
||||||
|
"chaserCheckpoint": 649023795,
|
||||||
|
"epochPosition": 551088596,
|
||||||
|
"epochNumber": 201,
|
||||||
|
"epochId": "d8f95f4b-167a-4487-9031-4d31a507e6d9",
|
||||||
|
"nodePriority": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"serverIp": "10.0.0.3",
|
||||||
|
"serverPort": 2112
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
const Long = require('long');
|
const Long = require('long');
|
||||||
|
|
||||||
const EMPTY_VERSION = Long.fromNumber(client.expectedVersion.emptyStream);
|
const EMPTY_VERSION = Long.fromNumber(client.expectedVersion.emptyStream);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
const adminCredentials = new client.UserCredentials("admin", "changeit");
|
const adminCredentials = new client.UserCredentials("admin", "changeit");
|
||||||
|
|
||||||
function createRandomEvent() {
|
function createRandomEvent() {
|
||||||
@ -9,20 +9,6 @@ function createRandomEvent() {
|
|||||||
|
|
||||||
var testStreamName = 'test-' + uuid.v4();
|
var testStreamName = 'test-' + uuid.v4();
|
||||||
|
|
||||||
function delay(ms) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
setTimeout(resolve, ms);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function delayOnlyFirst(count, action) {
|
|
||||||
if (count === 0) return action();
|
|
||||||
return delay(200)
|
|
||||||
.then(function () {
|
|
||||||
action();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'Test Create Persistent Subscription': function(test) {
|
'Test Create Persistent Subscription': function(test) {
|
||||||
var settings = client.PersistentSubscriptionSettings.create();
|
var settings = client.PersistentSubscriptionSettings.create();
|
||||||
@ -46,10 +32,8 @@ module.exports = {
|
|||||||
test.done();
|
test.done();
|
||||||
}
|
}
|
||||||
function eventAppeared(s, e) {
|
function eventAppeared(s, e) {
|
||||||
return delayOnlyFirst(receivedEvents.length, function () {
|
|
||||||
receivedEvents.push(e);
|
receivedEvents.push(e);
|
||||||
if (receivedEvents.length === 2) s.stop();
|
if (receivedEvents.length === 2) s.stop();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function subscriptionDropped(connection, reason, error) {
|
function subscriptionDropped(connection, reason, error) {
|
||||||
if (error) return done(error);
|
if (error) return done(error);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
const client = require('../src/client');
|
const client = require('../lib/dist');
|
||||||
const userCredentials = new client.UserCredentials('admin', 'changeit');
|
const userCredentials = new client.UserCredentials('admin', 'changeit');
|
||||||
|
|
||||||
const log = new client.NoopLogger();
|
const log = new client.NoopLogger();
|
||||||
const httpEndpoint = 'http://127.0.0.1:2113';
|
const httpEndpoint = `http://${process.env.EVENTSTORE_HOST || "localhost"}:2113`;
|
||||||
const operationTimeout = 5000;
|
const operationTimeout = 5000;
|
||||||
|
|
||||||
const simpleProjection = "\
|
const simpleProjection = "\
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
|
|
||||||
const numberOfStreams = 20;
|
const numberOfStreams = 20;
|
||||||
const maxBatch = 10;
|
const maxBatch = 10;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
|
|
||||||
const numberOfStreams = 20;
|
const numberOfStreams = 20;
|
||||||
const maxBatch = 10;
|
const maxBatch = 10;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
var Long = require('long');
|
var Long = require('long');
|
||||||
|
|
||||||
var NOSTREAM_VERSION = Long.fromNumber(client.expectedVersion.noStream);
|
var NOSTREAM_VERSION = Long.fromNumber(client.expectedVersion.noStream);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
var Long = require('long');
|
var Long = require('long');
|
||||||
|
|
||||||
const streamSize = 100;
|
const streamSize = 100;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
var Long = require('long');
|
var Long = require('long');
|
||||||
|
|
||||||
const streamSize = 100;
|
const streamSize = 100;
|
||||||
@ -18,7 +18,7 @@ module.exports = {
|
|||||||
.catch(cb);
|
.catch(cb);
|
||||||
},
|
},
|
||||||
'Read Stream Events Forward Happy Path': function(test) {
|
'Read Stream Events Forward Happy Path': function(test) {
|
||||||
test.expect(7 + (streamSize * 11));
|
test.expect(7 + (streamSize * 15));
|
||||||
var self = this;
|
var self = this;
|
||||||
this.conn.readStreamEventsForward(this.testStreamName, Long.fromNumber(0), streamSize)
|
this.conn.readStreamEventsForward(this.testStreamName, Long.fromNumber(0), streamSize)
|
||||||
.then(function(slice) {
|
.then(function(slice) {
|
||||||
|
@ -1,26 +1,12 @@
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
const allCredentials = new client.UserCredentials("admin", "changeit");
|
const allCredentials = new client.UserCredentials("admin", "changeit");
|
||||||
|
|
||||||
function createRandomEvent() {
|
function createRandomEvent() {
|
||||||
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
|
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
|
||||||
}
|
}
|
||||||
|
|
||||||
function delay(ms) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
setTimeout(resolve, ms);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function delayOnlyFirst(count, action) {
|
|
||||||
if (count === 0) return action();
|
|
||||||
return delay(200)
|
|
||||||
.then(function () {
|
|
||||||
action();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'Test Subscribe to All From (Start)': function(test) {
|
'Test Subscribe to All From (Start)': function(test) {
|
||||||
test.expect(6);
|
test.expect(6);
|
||||||
@ -48,14 +34,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
function eventAppeared(s, e) {
|
function eventAppeared(s, e) {
|
||||||
var isLive = liveProcessing;
|
var isLive = liveProcessing;
|
||||||
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
|
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
liveEvents.push(e);
|
liveEvents.push(e);
|
||||||
} else {
|
} else {
|
||||||
catchUpEvents.push(e);
|
catchUpEvents.push(e);
|
||||||
}
|
}
|
||||||
if (isLive && liveEvents.length === 2) s.stop();
|
if (isLive && liveEvents.length === 2) s.stop();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function liveProcessingStarted() {
|
function liveProcessingStarted() {
|
||||||
liveProcessing = true;
|
liveProcessing = true;
|
||||||
@ -99,14 +83,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
function eventAppeared(s, e) {
|
function eventAppeared(s, e) {
|
||||||
var isLive = liveProcessing;
|
var isLive = liveProcessing;
|
||||||
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
|
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
liveEvents.push(e);
|
liveEvents.push(e);
|
||||||
} else {
|
} else {
|
||||||
catchUpEvents.push(e);
|
catchUpEvents.push(e);
|
||||||
}
|
}
|
||||||
if (isLive && liveEvents.length === 2) s.stop();
|
if (isLive && liveEvents.length === 2) s.stop();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function liveProcessingStarted() {
|
function liveProcessingStarted() {
|
||||||
liveProcessing = true;
|
liveProcessing = true;
|
||||||
@ -149,14 +131,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
function eventAppeared(s, e) {
|
function eventAppeared(s, e) {
|
||||||
var isLive = liveProcessing;
|
var isLive = liveProcessing;
|
||||||
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
|
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
liveEvents.push(e);
|
liveEvents.push(e);
|
||||||
} else {
|
} else {
|
||||||
catchUpEvents.push(e);
|
catchUpEvents.push(e);
|
||||||
}
|
}
|
||||||
if (isLive && liveEvents.length === 2) s.stop();
|
if (isLive && liveEvents.length === 2) s.stop();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function liveProcessingStarted() {
|
function liveProcessingStarted() {
|
||||||
liveProcessing = true;
|
liveProcessing = true;
|
||||||
|
@ -1,21 +1,7 @@
|
|||||||
const uuid = require('uuid');
|
const uuid = require('uuid');
|
||||||
const client = require('../src/client');
|
const client = require('../lib/dist');
|
||||||
const allCredentials = new client.UserCredentials("admin", "changeit");
|
const allCredentials = new client.UserCredentials("admin", "changeit");
|
||||||
|
|
||||||
function delay(ms) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
setTimeout(resolve, ms);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function delayOnlyFirst(count, action) {
|
|
||||||
if (count === 0) return action();
|
|
||||||
return delay(200)
|
|
||||||
.then(function () {
|
|
||||||
action();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'Test Subscribe To All Happy Path': function(test) {
|
'Test Subscribe To All Happy Path': function(test) {
|
||||||
const resolveLinkTos = false;
|
const resolveLinkTos = false;
|
||||||
@ -44,10 +30,15 @@ module.exports = {
|
|||||||
|
|
||||||
var receivedEvents = [];
|
var receivedEvents = [];
|
||||||
function eventAppeared(subscription, event) {
|
function eventAppeared(subscription, event) {
|
||||||
delayOnlyFirst(receivedEvents.length, function() {
|
// Filter non-compliant events (only the one we've published)
|
||||||
|
let eventData;
|
||||||
|
try {
|
||||||
|
eventData = JSON.parse(event.event.data.toString());
|
||||||
|
} catch(e){}
|
||||||
|
if (eventData && eventData.a && eventData.b){
|
||||||
receivedEvents.push(event);
|
receivedEvents.push(event);
|
||||||
|
}
|
||||||
if (receivedEvents.length === numberOfPublishedEvents) subscription.close();
|
if (receivedEvents.length === numberOfPublishedEvents) subscription.close();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function subscriptionDropped(subscription, reason, error) {
|
function subscriptionDropped(subscription, reason, error) {
|
||||||
if (error) return done(error);
|
if (error) return done(error);
|
||||||
|
@ -1,28 +1,14 @@
|
|||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
var Long = require('long');
|
var Long = require('long');
|
||||||
|
|
||||||
function createRandomEvent() {
|
function createRandomEvent() {
|
||||||
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
|
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
|
||||||
}
|
}
|
||||||
|
|
||||||
function delay(ms) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
setTimeout(resolve, ms);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function delayOnlyFirst(count, action) {
|
|
||||||
if (count === 0) return action();
|
|
||||||
return delay(200)
|
|
||||||
.then(function () {
|
|
||||||
action();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'Test Subscribe to Stream From Beginning (null)': function(test) {
|
'Test Subscribe to Stream From Beginning (null)': function(test) {
|
||||||
test.expect(32);
|
test.expect(48);
|
||||||
var self = this;
|
var self = this;
|
||||||
var liveProcessing = false;
|
var liveProcessing = false;
|
||||||
var catchUpEvents = [];
|
var catchUpEvents = [];
|
||||||
@ -37,14 +23,12 @@ module.exports = {
|
|||||||
|
|
||||||
function eventAppeared(s, e) {
|
function eventAppeared(s, e) {
|
||||||
var isLive = liveProcessing;
|
var isLive = liveProcessing;
|
||||||
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
|
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
liveEvents.push(e);
|
liveEvents.push(e);
|
||||||
} else {
|
} else {
|
||||||
catchUpEvents.push(e);
|
catchUpEvents.push(e);
|
||||||
}
|
}
|
||||||
if (isLive && liveEvents.length === 2) s.stop();
|
if (isLive && liveEvents.length === 2) s.stop();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function liveProcessingStarted() {
|
function liveProcessingStarted() {
|
||||||
liveProcessing = true;
|
liveProcessing = true;
|
||||||
@ -78,7 +62,7 @@ module.exports = {
|
|||||||
.catch(test.done);
|
.catch(test.done);
|
||||||
},
|
},
|
||||||
'Test Subscribe to Stream From 0': function(test) {
|
'Test Subscribe to Stream From 0': function(test) {
|
||||||
test.expect(26);
|
test.expect(38);
|
||||||
var self = this;
|
var self = this;
|
||||||
var liveProcessing = false;
|
var liveProcessing = false;
|
||||||
var catchUpEvents = [];
|
var catchUpEvents = [];
|
||||||
@ -93,14 +77,12 @@ module.exports = {
|
|||||||
|
|
||||||
function eventAppeared(s, e) {
|
function eventAppeared(s, e) {
|
||||||
var isLive = liveProcessing;
|
var isLive = liveProcessing;
|
||||||
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
|
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
liveEvents.push(e);
|
liveEvents.push(e);
|
||||||
} else {
|
} else {
|
||||||
catchUpEvents.push(e);
|
catchUpEvents.push(e);
|
||||||
}
|
}
|
||||||
if (isLive && liveEvents.length === 2) s.stop();
|
if (isLive && liveEvents.length === 2) s.stop();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function liveProcessingStarted() {
|
function liveProcessingStarted() {
|
||||||
liveProcessing = true;
|
liveProcessing = true;
|
||||||
|
@ -1,21 +1,7 @@
|
|||||||
const uuid = require('uuid');
|
const uuid = require('uuid');
|
||||||
const client = require('../src/client');
|
const client = require('../lib/dist');
|
||||||
const Long = require('long');
|
const Long = require('long');
|
||||||
|
|
||||||
function delay(ms) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
setTimeout(resolve, ms);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function delayOnlyFirst(count, action) {
|
|
||||||
if (count === 0) return action();
|
|
||||||
return delay(200)
|
|
||||||
.then(function () {
|
|
||||||
action();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'Test Subscribe To Stream Happy Path': function(test) {
|
'Test Subscribe To Stream Happy Path': function(test) {
|
||||||
const resolveLinkTos = false;
|
const resolveLinkTos = false;
|
||||||
@ -43,10 +29,15 @@ module.exports = {
|
|||||||
|
|
||||||
var receivedEvents = [];
|
var receivedEvents = [];
|
||||||
function eventAppeared(subscription, event) {
|
function eventAppeared(subscription, event) {
|
||||||
delayOnlyFirst(receivedEvents.length, function () {
|
// Filter non-compliant events (only the one we've published)
|
||||||
|
let eventData;
|
||||||
|
try {
|
||||||
|
eventData = JSON.parse(event.event.data.toString());
|
||||||
|
} catch(e){}
|
||||||
|
if (eventData && eventData.a && eventData.b){
|
||||||
receivedEvents.push(event);
|
receivedEvents.push(event);
|
||||||
|
}
|
||||||
if (receivedEvents.length === numberOfPublishedEvents) subscription.close();
|
if (receivedEvents.length === numberOfPublishedEvents) subscription.close();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function subscriptionDropped(subscription, reason, error) {
|
function subscriptionDropped(subscription, reason, error) {
|
||||||
if (error) return done(error);
|
if (error) return done(error);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
var uuid = require('uuid');
|
var uuid = require('uuid');
|
||||||
var Long = require('long');
|
var Long = require('long');
|
||||||
var client = require('../src/client');
|
var client = require('../lib/dist');
|
||||||
|
|
||||||
var ANY_VERSION = Long.fromNumber(client.expectedVersion.any);
|
var ANY_VERSION = Long.fromNumber(client.expectedVersion.any);
|
||||||
var NOSTREAM_VERSION = Long.fromNumber(client.expectedVersion.noStream);
|
var NOSTREAM_VERSION = Long.fromNumber(client.expectedVersion.noStream);
|
||||||
|
75
test/unit/common/utils/shuffle.test.js
Normal file
75
test/unit/common/utils/shuffle.test.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const shuffle = require('../../../../src/common/utils/shuffle');
|
||||||
|
|
||||||
|
describe('shuffle', () => {
|
||||||
|
test('Should return a shuffle array (same size, different order)', async () => {
|
||||||
|
// arrange
|
||||||
|
const tArray = [1,2,3,4,5,6,7,8,9];
|
||||||
|
// act
|
||||||
|
const result = shuffle(tArray);
|
||||||
|
// assert
|
||||||
|
expect(result).toHaveLength(tArray.length);
|
||||||
|
expect(result).not.toEqual(tArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return a shuffle array for array of size 1', async () => {
|
||||||
|
// arrange
|
||||||
|
const tArray = [1];
|
||||||
|
// act
|
||||||
|
const result = shuffle(tArray);
|
||||||
|
// assert
|
||||||
|
expect(result).toHaveLength(tArray.length);
|
||||||
|
expect(result).toEqual(tArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return a shuffle array for array of size 0', async () => {
|
||||||
|
// arrange
|
||||||
|
const tArray = [];
|
||||||
|
// act
|
||||||
|
const result = shuffle(tArray);
|
||||||
|
// assert
|
||||||
|
expect(result).toHaveLength(tArray.length);
|
||||||
|
expect(result).toEqual(tArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return a shuffle only from a starting point', async () => {
|
||||||
|
// arrange
|
||||||
|
const tArray = [1,2,3,4,5,6,7,8,9];
|
||||||
|
// act
|
||||||
|
const result = shuffle(tArray, 2);
|
||||||
|
// assert
|
||||||
|
expect(result).toHaveLength(tArray.length);
|
||||||
|
expect(result).not.toEqual(tArray);
|
||||||
|
const fixedOrigin = [...tArray].splice(0, 2);
|
||||||
|
const shuffledOrigin = [...tArray].splice(2);
|
||||||
|
const fixedResult= [...result].splice(0, 2);
|
||||||
|
const shuffledResult = [...result].splice(2);
|
||||||
|
expect(fixedResult).toHaveLength(fixedOrigin.length);
|
||||||
|
expect(fixedResult).toEqual(fixedOrigin);
|
||||||
|
expect(shuffledResult).toHaveLength(shuffledOrigin.length);
|
||||||
|
expect(shuffledResult).not.toEqual(shuffledOrigin);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return a shuffle only from a starting point to and end point', async () => {
|
||||||
|
// arrange
|
||||||
|
const tArray = [1,2,3,4,5,6,7,8,9];
|
||||||
|
// act
|
||||||
|
const result = shuffle(tArray, 2, 4);
|
||||||
|
// assert
|
||||||
|
expect(result).toHaveLength(tArray.length);
|
||||||
|
expect(result).not.toEqual(tArray);
|
||||||
|
const fixedStartOrigin = [...tArray].splice(0, 2);
|
||||||
|
const fixedEndOrigin = [...tArray].splice(4);
|
||||||
|
const shuffledOrigin = [...tArray].splice(2, 4);
|
||||||
|
const fixedStartResult= [...result].splice(0, 2);
|
||||||
|
const fixedEndResult = [...tArray].splice(4);
|
||||||
|
const shuffledResult = [...result].splice(2, 4);
|
||||||
|
expect(fixedStartResult).toHaveLength(fixedStartOrigin.length);
|
||||||
|
expect(fixedStartResult).toEqual(fixedStartOrigin);
|
||||||
|
|
||||||
|
expect(fixedEndResult).toHaveLength(fixedEndOrigin.length);
|
||||||
|
expect(fixedEndResult).toEqual(fixedEndOrigin);
|
||||||
|
|
||||||
|
expect(shuffledResult).toHaveLength(shuffledOrigin.length);
|
||||||
|
expect(shuffledResult).not.toEqual(shuffledOrigin);
|
||||||
|
});
|
||||||
|
});
|
693
test/unit/core/clusterDiscoverer.test.js
Normal file
693
test/unit/core/clusterDiscoverer.test.js
Normal file
@ -0,0 +1,693 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const dns = require('dns');
|
||||||
|
|
||||||
|
const ClusterDiscoverer = require('../../../src/core/cluster/clusterDiscoverer');
|
||||||
|
const ClusterInfo = require('../../../src/core/cluster/clusterInfo');
|
||||||
|
const GossipSeed = require('../../../src/gossipSeed');
|
||||||
|
const NodeEndPoints = require('../../../src/core/cluster/nodeEndpoints');
|
||||||
|
|
||||||
|
const logger = { info: () => {} };
|
||||||
|
|
||||||
|
describe('ClusterDiscoverer', () => {
|
||||||
|
const mockDns = {
|
||||||
|
ADDRCONFIG: dns.ADDRCONFIG,
|
||||||
|
V4MAPPED: dns.V4MAPPED,
|
||||||
|
};
|
||||||
|
const mockHttp = {};
|
||||||
|
const settings = {
|
||||||
|
clusterDns: 'my-discover.com:2113',
|
||||||
|
maxDiscoverAttempts: 10,
|
||||||
|
discoverDelay: 10,
|
||||||
|
managerExternalHttpPort: 2113,
|
||||||
|
seeds: null,
|
||||||
|
gossipTimeout: 1000,
|
||||||
|
};
|
||||||
|
const tClusterInfo = new ClusterInfo([
|
||||||
|
{
|
||||||
|
instanceId: 'bb16857d-373d-4233-a175-89c917a72329',
|
||||||
|
timeStamp: '2020-09-02T13:53:24.234898Z',
|
||||||
|
state: 'Slave',
|
||||||
|
isAlive: false,
|
||||||
|
internalTcpIp: '10.0.0.1',
|
||||||
|
internalTcpPort: 1112,
|
||||||
|
internalSecureTcpPort: 0,
|
||||||
|
externalTcpIp: '10.0.0.1',
|
||||||
|
externalTcpPort: 1113,
|
||||||
|
externalSecureTcpPort: 0,
|
||||||
|
internalHttpIp: '10.0.0.1',
|
||||||
|
internalHttpPort: 2112,
|
||||||
|
externalHttpIp: '10.0.0.1',
|
||||||
|
externalHttpPort: 2113,
|
||||||
|
lastCommitPosition: 648923382,
|
||||||
|
writerCheckpoint: 648936339,
|
||||||
|
chaserCheckpoint: 648936339,
|
||||||
|
epochPosition: 551088596,
|
||||||
|
epochNumber: 201,
|
||||||
|
epochId: 'd8f95f4b-167a-4487-9031-4d31a507e6d9',
|
||||||
|
nodePriority: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
instanceId: 'b3c18dcd-6476-467a-b7b8-d6672b74e9c2',
|
||||||
|
timeStamp: '2020-09-02T13:56:06.189428Z',
|
||||||
|
state: 'CatchingUp',
|
||||||
|
isAlive: true,
|
||||||
|
internalTcpIp: '10.0.0.2',
|
||||||
|
internalTcpPort: 1112,
|
||||||
|
internalSecureTcpPort: 0,
|
||||||
|
externalTcpIp: '10.0.0.2',
|
||||||
|
externalTcpPort: 1113,
|
||||||
|
externalSecureTcpPort: 0,
|
||||||
|
internalHttpIp: '10.0.0.2',
|
||||||
|
internalHttpPort: 2112,
|
||||||
|
externalHttpIp: '10.0.0.2',
|
||||||
|
externalHttpPort: 2113,
|
||||||
|
lastCommitPosition: -1,
|
||||||
|
writerCheckpoint: 0,
|
||||||
|
chaserCheckpoint: 0,
|
||||||
|
epochPosition: -1,
|
||||||
|
epochNumber: -1,
|
||||||
|
epochId: '00000000-0000-0000-0000-000000000000',
|
||||||
|
nodePriority: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
instanceId: 'e802a2b5-826c-4bd5-84d0-c9d1387fbf79',
|
||||||
|
timeStamp: '2020-09-02T13:56:07.391534Z',
|
||||||
|
state: 'Master',
|
||||||
|
isAlive: true,
|
||||||
|
internalTcpIp: '10.0.0.3',
|
||||||
|
internalTcpPort: 1112,
|
||||||
|
internalSecureTcpPort: 0,
|
||||||
|
externalTcpIp: '10.0.0.3',
|
||||||
|
externalTcpPort: 1113,
|
||||||
|
externalSecureTcpPort: 0,
|
||||||
|
internalHttpIp: '10.0.0.3',
|
||||||
|
internalHttpPort: 2112,
|
||||||
|
externalHttpIp: '10.0.0.3',
|
||||||
|
externalHttpPort: 2113,
|
||||||
|
lastCommitPosition: 649007631,
|
||||||
|
writerCheckpoint: 649024685,
|
||||||
|
chaserCheckpoint: 649024685,
|
||||||
|
epochPosition: 649023795,
|
||||||
|
epochNumber: 202,
|
||||||
|
epochId: '1f17695d-6558-4d8b-ba60-2ae273b11e09',
|
||||||
|
nodePriority: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
instanceId: '24bb9031-5f21-436c-a7b5-c5f03a95e938',
|
||||||
|
timeStamp: '2020-09-02T13:54:39.023053Z',
|
||||||
|
state: 'Slave',
|
||||||
|
isAlive: false,
|
||||||
|
internalTcpIp: '10.0.0.4',
|
||||||
|
internalTcpPort: 1112,
|
||||||
|
internalSecureTcpPort: 0,
|
||||||
|
externalTcpIp: '10.0.0.4',
|
||||||
|
externalTcpPort: 1113,
|
||||||
|
externalSecureTcpPort: 0,
|
||||||
|
internalHttpIp: '10.0.0.4',
|
||||||
|
internalHttpPort: 2112,
|
||||||
|
externalHttpIp: '10.0.0.4',
|
||||||
|
externalHttpPort: 2113,
|
||||||
|
lastCommitPosition: 649007631,
|
||||||
|
writerCheckpoint: 649023795,
|
||||||
|
chaserCheckpoint: 649023795,
|
||||||
|
epochPosition: 551088596,
|
||||||
|
epochNumber: 201,
|
||||||
|
epochId: 'd8f95f4b-167a-4487-9031-4d31a507e6d9',
|
||||||
|
nodePriority: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const tClusterInfoNoBestNode = new ClusterInfo([
|
||||||
|
{
|
||||||
|
instanceId: 'bb16857d-373d-4233-a175-89c917a72329',
|
||||||
|
timeStamp: '2020-09-02T13:53:24.234898Z',
|
||||||
|
state: 'Manager',
|
||||||
|
isAlive: true,
|
||||||
|
internalTcpIp: '10.0.0.1',
|
||||||
|
internalTcpPort: 1112,
|
||||||
|
internalSecureTcpPort: 0,
|
||||||
|
externalTcpIp: '10.0.0.1',
|
||||||
|
externalTcpPort: 1113,
|
||||||
|
externalSecureTcpPort: 0,
|
||||||
|
internalHttpIp: '10.0.0.1',
|
||||||
|
internalHttpPort: 2112,
|
||||||
|
externalHttpIp: '10.0.0.1',
|
||||||
|
externalHttpPort: 2113,
|
||||||
|
lastCommitPosition: 648923382,
|
||||||
|
writerCheckpoint: 648936339,
|
||||||
|
chaserCheckpoint: 648936339,
|
||||||
|
epochPosition: 551088596,
|
||||||
|
epochNumber: 201,
|
||||||
|
epochId: 'd8f95f4b-167a-4487-9031-4d31a507e6d9',
|
||||||
|
nodePriority: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
instanceId: 'b3c18dcd-6476-467a-b7b8-d6672b74e9c2',
|
||||||
|
timeStamp: '2020-09-02T13:56:06.189428Z',
|
||||||
|
state: 'CatchingUp',
|
||||||
|
isAlive: false,
|
||||||
|
internalTcpIp: '10.0.0.2',
|
||||||
|
internalTcpPort: 1112,
|
||||||
|
internalSecureTcpPort: 0,
|
||||||
|
externalTcpIp: '10.0.0.2',
|
||||||
|
externalTcpPort: 1113,
|
||||||
|
externalSecureTcpPort: 0,
|
||||||
|
internalHttpIp: '10.0.0.2',
|
||||||
|
internalHttpPort: 2112,
|
||||||
|
externalHttpIp: '10.0.0.2',
|
||||||
|
externalHttpPort: 2113,
|
||||||
|
lastCommitPosition: -1,
|
||||||
|
writerCheckpoint: 0,
|
||||||
|
chaserCheckpoint: 0,
|
||||||
|
epochPosition: -1,
|
||||||
|
epochNumber: -1,
|
||||||
|
epochId: '00000000-0000-0000-0000-000000000000',
|
||||||
|
nodePriority: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
instanceId: 'e802a2b5-826c-4bd5-84d0-c9d1387fbf79',
|
||||||
|
timeStamp: '2020-09-02T13:56:07.391534Z',
|
||||||
|
state: 'Master',
|
||||||
|
isAlive: false,
|
||||||
|
internalTcpIp: '10.0.0.3',
|
||||||
|
internalTcpPort: 1112,
|
||||||
|
internalSecureTcpPort: 0,
|
||||||
|
externalTcpIp: '10.0.0.3',
|
||||||
|
externalTcpPort: 1113,
|
||||||
|
externalSecureTcpPort: 0,
|
||||||
|
internalHttpIp: '10.0.0.3',
|
||||||
|
internalHttpPort: 2112,
|
||||||
|
externalHttpIp: '10.0.0.3',
|
||||||
|
externalHttpPort: 2113,
|
||||||
|
lastCommitPosition: 649007631,
|
||||||
|
writerCheckpoint: 649024685,
|
||||||
|
chaserCheckpoint: 649024685,
|
||||||
|
epochPosition: 649023795,
|
||||||
|
epochNumber: 202,
|
||||||
|
epochId: '1f17695d-6558-4d8b-ba60-2ae273b11e09',
|
||||||
|
nodePriority: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
instanceId: '24bb9031-5f21-436c-a7b5-c5f03a95e938',
|
||||||
|
timeStamp: '2020-09-02T13:54:39.023053Z',
|
||||||
|
state: 'Slave',
|
||||||
|
isAlive: false,
|
||||||
|
internalTcpIp: '10.0.0.4',
|
||||||
|
internalTcpPort: 1112,
|
||||||
|
internalSecureTcpPort: 0,
|
||||||
|
externalTcpIp: '10.0.0.4',
|
||||||
|
externalTcpPort: 1113,
|
||||||
|
externalSecureTcpPort: 0,
|
||||||
|
internalHttpIp: '10.0.0.4',
|
||||||
|
internalHttpPort: 2112,
|
||||||
|
externalHttpIp: '10.0.0.4',
|
||||||
|
externalHttpPort: 2113,
|
||||||
|
lastCommitPosition: 649007631,
|
||||||
|
writerCheckpoint: 649023795,
|
||||||
|
chaserCheckpoint: 649023795,
|
||||||
|
epochPosition: 551088596,
|
||||||
|
epochNumber: 201,
|
||||||
|
epochId: 'd8f95f4b-167a-4487-9031-4d31a507e6d9',
|
||||||
|
nodePriority: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const discoverer = new ClusterDiscoverer(logger, settings, mockDns, mockHttp);
|
||||||
|
const discovererWithGossipSeeds = new ClusterDiscoverer(
|
||||||
|
logger,
|
||||||
|
{
|
||||||
|
...settings,
|
||||||
|
...{
|
||||||
|
seeds: [
|
||||||
|
new GossipSeed({
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: 2113,
|
||||||
|
}),
|
||||||
|
new GossipSeed({
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: 2113,
|
||||||
|
}),
|
||||||
|
new GossipSeed({
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: 2113,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mockDns,
|
||||||
|
mockHttp
|
||||||
|
);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
test('Should be defined', () => {
|
||||||
|
expect(discoverer).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should throw an error', () => {
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ClusterDiscoverer(
|
||||||
|
logger,
|
||||||
|
{
|
||||||
|
clusterDns: null,
|
||||||
|
maxDiscoverAttempts: 10,
|
||||||
|
managerExternalHttpPort: 2113,
|
||||||
|
seeds: null,
|
||||||
|
gossipTimeout: 1000,
|
||||||
|
},
|
||||||
|
mockDns,
|
||||||
|
mockHttp
|
||||||
|
)
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
new ClusterDiscoverer(
|
||||||
|
logger,
|
||||||
|
{
|
||||||
|
clusterDns: null,
|
||||||
|
maxDiscoverAttempts: 10,
|
||||||
|
managerExternalHttpPort: 2113,
|
||||||
|
seeds: [],
|
||||||
|
gossipTimeout: 1000,
|
||||||
|
},
|
||||||
|
mockDns,
|
||||||
|
mockHttp
|
||||||
|
)
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_resolveDns', () => {
|
||||||
|
test('Should call lookup', async () => {
|
||||||
|
mockDns.lookup = jest.fn().mockResolvedValue([
|
||||||
|
{
|
||||||
|
address: '10.0.0.1',
|
||||||
|
family: 4,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await discoverer._resolveDns('my-discover.com:2113');
|
||||||
|
expect(mockDns.lookup).toHaveBeenCalledWith('my-discover.com:2113', {
|
||||||
|
family: 4,
|
||||||
|
hints: dns.ADDRCONFIG | dns.V4MAPPED,
|
||||||
|
all: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should reject if dnsService fails', async () => {
|
||||||
|
mockDns.lookup = jest.fn().mockRejectedValue(new Error('Unexpected DNS error'));
|
||||||
|
await expect(discoverer._resolveDns('my-discover.com:2113')).rejects.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should reject if no addresses are returned', async () => {
|
||||||
|
mockDns.lookup = jest.fn().mockResolvedValue([]);
|
||||||
|
await expect(discoverer._resolveDns('my-discover.com:2113')).rejects.toEqual(
|
||||||
|
new Error('No result from dns lookup for my-discover.com:2113')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return a list of candidate addresses', async () => {
|
||||||
|
mockDns.lookup = jest.fn().mockResolvedValue([
|
||||||
|
{
|
||||||
|
address: '10.0.0.1',
|
||||||
|
family: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: '10.0.0.2',
|
||||||
|
family: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: '10.0.0.3',
|
||||||
|
family: 4,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const candidates = await discoverer._resolveDns('my-discover.com:2113');
|
||||||
|
expect(candidates).toEqual(['10.0.0.1', '10.0.0.2', '10.0.0.3']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_clusterInfo', () => {
|
||||||
|
test('Should call httpService.request to get cluster informations', async () => {
|
||||||
|
const tCandidate = new GossipSeed(
|
||||||
|
{
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: '2113',
|
||||||
|
},
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
const tTimeout = 1000;
|
||||||
|
const requestEvents = {};
|
||||||
|
let responseCallback;
|
||||||
|
mockHttp.request = jest.fn((options, callback) => {
|
||||||
|
responseCallback = callback;
|
||||||
|
return {
|
||||||
|
setTimeout: jest.fn(() => ({})),
|
||||||
|
on: (type, callback) => {
|
||||||
|
requestEvents[type] = callback;
|
||||||
|
},
|
||||||
|
end: () => {},
|
||||||
|
destroy: () => {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
discoverer._clusterInfo(tCandidate, tTimeout);
|
||||||
|
expect(mockHttp.request).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
host: tCandidate.endPoint.host,
|
||||||
|
port: tCandidate.endPoint.port,
|
||||||
|
path: '/gossip?format=json',
|
||||||
|
timeout: tTimeout,
|
||||||
|
},
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should call httpService.request to get cluster informations with host header', async () => {
|
||||||
|
const tCandidate = new GossipSeed(
|
||||||
|
{
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: '2113',
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
'MyHost'
|
||||||
|
);
|
||||||
|
const tTimeout = 1000;
|
||||||
|
const requestEvents = {};
|
||||||
|
let responseCallback;
|
||||||
|
mockHttp.request = jest.fn((options, callback) => {
|
||||||
|
responseCallback = callback;
|
||||||
|
return {
|
||||||
|
setTimeout: jest.fn(() => ({})),
|
||||||
|
on: (type, callback) => {
|
||||||
|
requestEvents[type] = callback;
|
||||||
|
},
|
||||||
|
end: () => {},
|
||||||
|
destroy: () => {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
discoverer._clusterInfo(tCandidate, tTimeout);
|
||||||
|
expect(mockHttp.request).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
host: tCandidate.endPoint.host,
|
||||||
|
port: tCandidate.endPoint.port,
|
||||||
|
path: '/gossip?format=json',
|
||||||
|
timeout: tTimeout,
|
||||||
|
headers: {
|
||||||
|
Host: tCandidate.hostHeader,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return a timeout error if the sockets fails to be connected in the specified timeout', async () => {
|
||||||
|
const tCandidate = new GossipSeed({
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: '2113',
|
||||||
|
});
|
||||||
|
const tTimeout = 1000;
|
||||||
|
const requestEvents = {};
|
||||||
|
let responseCallback;
|
||||||
|
mockHttp.request = jest.fn((options, callback) => {
|
||||||
|
responseCallback = callback;
|
||||||
|
return {
|
||||||
|
setTimeout: jest.fn(() => ({})),
|
||||||
|
on: (type, callback) => {
|
||||||
|
requestEvents[type] = callback;
|
||||||
|
},
|
||||||
|
end: () => {
|
||||||
|
requestEvents['timeout']();
|
||||||
|
},
|
||||||
|
destroy: () => {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await expect(discoverer._clusterInfo(tCandidate, tTimeout)).rejects.toThrow(
|
||||||
|
new Error('Connection to gossip timed out')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return an error if the http request emits an error', async () => {
|
||||||
|
const tCandidate = new GossipSeed({
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: '2113',
|
||||||
|
});
|
||||||
|
const tTimeout = 1000;
|
||||||
|
const requestEvents = {};
|
||||||
|
let responseCallback;
|
||||||
|
mockHttp.request = jest.fn((options, callback) => {
|
||||||
|
responseCallback = callback;
|
||||||
|
return {
|
||||||
|
setTimeout: jest.fn(() => ({})),
|
||||||
|
on: (type, callback) => {
|
||||||
|
requestEvents[type] = callback;
|
||||||
|
},
|
||||||
|
end: () => {
|
||||||
|
requestEvents['error'](new Error('Request error'));
|
||||||
|
},
|
||||||
|
destroy: () => {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await expect(discoverer._clusterInfo(tCandidate, tTimeout)).rejects.toThrow(
|
||||||
|
new Error('Connection to gossip errored')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should return an error if the candidate doesn't returns a 200 code", async () => {
|
||||||
|
const tCandidate = new GossipSeed({
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: '2113',
|
||||||
|
});
|
||||||
|
const tTimeout = 1000;
|
||||||
|
const requestEvents = {};
|
||||||
|
let responseCallback;
|
||||||
|
mockHttp.request = jest.fn((options, callback) => {
|
||||||
|
responseCallback = callback;
|
||||||
|
return {
|
||||||
|
setTimeout: jest.fn(() => ({})),
|
||||||
|
on: (type, callback) => {
|
||||||
|
requestEvents[type] = callback;
|
||||||
|
},
|
||||||
|
end: () => {
|
||||||
|
callback({
|
||||||
|
statusCode: 503,
|
||||||
|
on: (type, callback) => {
|
||||||
|
responseEvents[type] = callback;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: () => {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await expect(discoverer._clusterInfo(tCandidate, tTimeout)).rejects.toThrow(
|
||||||
|
new Error('Gossip candidate returns a 503 error')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return an error if the response is not a valid JSON', async () => {
|
||||||
|
const tCandidate = new GossipSeed({
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: '2113',
|
||||||
|
});
|
||||||
|
const tTimeout = 1000;
|
||||||
|
let responseCallback;
|
||||||
|
const requestEvents = {};
|
||||||
|
const responseEvents = {};
|
||||||
|
mockHttp.request = jest.fn((options, callback) => {
|
||||||
|
responseCallback = callback;
|
||||||
|
return {
|
||||||
|
setTimeout: jest.fn(() => ({})),
|
||||||
|
on: (type, callback) => {
|
||||||
|
requestEvents[type] = callback;
|
||||||
|
},
|
||||||
|
end: () => {
|
||||||
|
callback({
|
||||||
|
statusCode: 200,
|
||||||
|
on: (type, callback) => {
|
||||||
|
responseEvents[type] = callback;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
responseEvents['data']('Not a JSON response');
|
||||||
|
responseEvents['end']();
|
||||||
|
},
|
||||||
|
destroy: () => {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await expect(discoverer._clusterInfo(tCandidate, tTimeout)).rejects.toThrow(
|
||||||
|
new Error('Unable to parse gossip response')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return the member informations for the cluster', async () => {
|
||||||
|
const tCandidate = new GossipSeed({
|
||||||
|
host: '10.0.0.1',
|
||||||
|
port: '2113',
|
||||||
|
});
|
||||||
|
const tTimeout = 1000;
|
||||||
|
const requestEvents = {};
|
||||||
|
const responseEvents = {};
|
||||||
|
mockHttp.request = jest.fn((options, callback) => {
|
||||||
|
return {
|
||||||
|
setTimeout: jest.fn(() => ({})),
|
||||||
|
on: (type, callback) => {
|
||||||
|
requestEvents[type] = callback;
|
||||||
|
},
|
||||||
|
end: () => {
|
||||||
|
callback({
|
||||||
|
statusCode: 200,
|
||||||
|
on: (type, callback) => {
|
||||||
|
responseEvents[type] = callback;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
responseEvents['data'](fs.readFileSync(path.resolve(__dirname, '../../fixtures/gossip.json')));
|
||||||
|
responseEvents['end']();
|
||||||
|
},
|
||||||
|
destroy: () => {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const infos = await discoverer._clusterInfo(tCandidate, tTimeout);
|
||||||
|
expect(infos).toEqual(tClusterInfo);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_getGossipCandidates', () => {
|
||||||
|
test('Should get from dns if gossipSeeds are empty', async () => {
|
||||||
|
discoverer._resolveDns = jest.fn().mockResolvedValue(['10.0.0.1', '10.0.0.2', '10.0.0.3']);
|
||||||
|
const candidates = await discoverer._getGossipCandidates(settings.managerExternalHttpPort);
|
||||||
|
expect(discoverer._resolveDns).toHaveBeenCalled();
|
||||||
|
expect(candidates).toHaveLength(3);
|
||||||
|
for (let i in candidates) {
|
||||||
|
expect(candidates[i]).toBeInstanceOf(GossipSeed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should get gossipSeeds if present', async () => {
|
||||||
|
discovererWithGossipSeeds._resolveDns = jest.fn();
|
||||||
|
const candidates = await discovererWithGossipSeeds._getGossipCandidates(settings.managerExternalHttpPort);
|
||||||
|
expect(discovererWithGossipSeeds._resolveDns).not.toHaveBeenCalled();
|
||||||
|
expect(candidates).toHaveLength(3);
|
||||||
|
for (let i in candidates) {
|
||||||
|
expect(candidates[i]).toBeInstanceOf(GossipSeed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#discover', () => {
|
||||||
|
test('Should get resolve dns discover url to get IP addresses of the eventstore node', async () => {
|
||||||
|
discoverer._resolveDns = jest.fn().mockResolvedValue(['10.0.0.1', '10.0.0.2', '10.0.0.3']);
|
||||||
|
discoverer._clusterInfo = jest.fn().mockResolvedValue(tClusterInfo);
|
||||||
|
await discoverer.discover();
|
||||||
|
expect(discoverer._resolveDns).toHaveBeenCalledWith(settings.clusterDns);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should call _clusterInfo with candidate', async () => {
|
||||||
|
discoverer._resolveDns = jest.fn().mockResolvedValue(['10.0.0.1', '10.0.0.2', '10.0.0.3']);
|
||||||
|
discoverer._clusterInfo = jest.fn().mockResolvedValue(tClusterInfo);
|
||||||
|
await discoverer.discover();
|
||||||
|
expect(discoverer._clusterInfo).toHaveBeenCalledWith(
|
||||||
|
new GossipSeed({ host: '10.0.0.1', port: settings.managerExternalHttpPort }),
|
||||||
|
settings.gossipTimeout
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should call _clusterInfo with candidate from gossipSeed if provided', async () => {
|
||||||
|
discovererWithGossipSeeds._resolveDns = jest.fn().mockResolvedValue();
|
||||||
|
discovererWithGossipSeeds._clusterInfo = jest.fn().mockResolvedValue(tClusterInfo);
|
||||||
|
await discovererWithGossipSeeds.discover();
|
||||||
|
expect(discovererWithGossipSeeds._resolveDns).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return the bestNode', async () => {
|
||||||
|
discoverer._resolveDns = jest.fn().mockResolvedValue(['10.0.0.1', '10.0.0.2', '10.0.0.3']);
|
||||||
|
discoverer._clusterInfo = jest.fn().mockResolvedValue(tClusterInfo);
|
||||||
|
const node = await discoverer.discover();
|
||||||
|
expect(node).toEqual(
|
||||||
|
new NodeEndPoints(
|
||||||
|
{
|
||||||
|
host: '10.0.0.3',
|
||||||
|
port: 1113,
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should try to call each candidates until it get clusterInfo with bestNode', async () => {
|
||||||
|
discoverer._resolveDns = jest.fn().mockResolvedValue(['10.0.0.1', '10.0.0.2', '10.0.0.3']);
|
||||||
|
discoverer._clusterInfo = jest.fn().mockImplementation(async (candidate) => {
|
||||||
|
if (candidate.endPoint.host === '10.0.0.3') {
|
||||||
|
return tClusterInfo;
|
||||||
|
}
|
||||||
|
throw new Error('Gossip candidate returns a 503 error');
|
||||||
|
});
|
||||||
|
const node = await discoverer.discover();
|
||||||
|
expect(node).toEqual(
|
||||||
|
new NodeEndPoints(
|
||||||
|
{
|
||||||
|
host: '10.0.0.3',
|
||||||
|
port: 1113,
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(discoverer._clusterInfo).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should fail if the we reach the maxDiscoverAttempts limits (no bestNode is found)', async () => {
|
||||||
|
discoverer._resolveDns = jest.fn().mockResolvedValue(['10.0.0.1', '10.0.0.2', '10.0.0.3']);
|
||||||
|
discoverer._clusterInfo = jest.fn().mockResolvedValue(tClusterInfoNoBestNode);
|
||||||
|
await expect(discoverer.discover()).rejects.toEqual(
|
||||||
|
new Error(`Failed to discover candidate in ${settings.maxDiscoverAttempts} attempts.`)
|
||||||
|
);
|
||||||
|
expect(discoverer._resolveDns).toHaveBeenCalledTimes(settings.maxDiscoverAttempts);
|
||||||
|
expect(discoverer._resolveDns).toHaveBeenCalledTimes(settings.maxDiscoverAttempts);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should fail if the we reach the maxDiscoverAttempts limits (all resolveDns attempts fails)', async () => {
|
||||||
|
discoverer._resolveDns = jest.fn().mockRejectedValue(new Error('Connection to gossip timed out'));
|
||||||
|
await expect(discoverer.discover()).rejects.toEqual(
|
||||||
|
new Error(`Failed to discover candidate in ${settings.maxDiscoverAttempts} attempts.`)
|
||||||
|
);
|
||||||
|
expect(discoverer._resolveDns).toHaveBeenCalledTimes(settings.maxDiscoverAttempts);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should fail if the we reach the maxDiscoverAttempts limits (all clusterInfo attempts fails)', async () => {
|
||||||
|
discoverer._resolveDns = jest.fn().mockResolvedValue(['10.0.0.1', '10.0.0.2', '10.0.0.3']);
|
||||||
|
discoverer._clusterInfo = jest.fn().mockRejectedValue(new Error('Gossip candidate returns a 503 error'));
|
||||||
|
await expect(discoverer.discover()).rejects.toEqual(
|
||||||
|
new Error(`Failed to discover candidate in ${settings.maxDiscoverAttempts} attempts.`)
|
||||||
|
);
|
||||||
|
expect(discoverer._resolveDns).toHaveBeenCalledTimes(settings.maxDiscoverAttempts);
|
||||||
|
expect(discoverer._resolveDns).toHaveBeenCalledTimes(settings.maxDiscoverAttempts);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should try to call each candidates expect failed one until it get clusterInfo with bestNode', async () => {
|
||||||
|
discoverer._resolveDns = jest.fn().mockResolvedValue(['10.0.0.1', '10.0.0.2', '10.0.0.3']);
|
||||||
|
discoverer._clusterInfo = jest.fn().mockImplementation(async (candidate) => {
|
||||||
|
if (candidate.endPoint.host === '10.0.0.3') {
|
||||||
|
return tClusterInfo;
|
||||||
|
}
|
||||||
|
throw new Error('Gossip candidate returns a 503 error');
|
||||||
|
});
|
||||||
|
const node = await discoverer.discover({ host: '10.0.0.2', port: 2113 });
|
||||||
|
expect(node).toEqual(
|
||||||
|
new NodeEndPoints(
|
||||||
|
{
|
||||||
|
host: '10.0.0.3',
|
||||||
|
port: 1113,
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(discoverer._clusterInfo).toHaveBeenCalledTimes(2);
|
||||||
|
expect(discoverer._clusterInfo).toHaveBeenCalledWith(
|
||||||
|
new GossipSeed({ host: '10.0.0.1', port: 2113 }),
|
||||||
|
settings.gossipTimeout
|
||||||
|
);
|
||||||
|
expect(discoverer._clusterInfo).toHaveBeenCalledWith(
|
||||||
|
new GossipSeed({ host: '10.0.0.3', port: 2113 }),
|
||||||
|
settings.gossipTimeout
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
71
tools/fake-server.js
Normal file
71
tools/fake-server.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const net = require('net');
|
||||||
|
const util = require('util');
|
||||||
|
const createBufferSegment = require('../src/common/bufferSegment');
|
||||||
|
const TcpPackage = require('../src/systemData/tcpPackage');
|
||||||
|
const TcpCommand = require('../src/systemData/tcpCommand');
|
||||||
|
const TcpFlags = require('../src/systemData/tcpFlags');
|
||||||
|
const ClientMessages = require('../src/messages/clientMessage');
|
||||||
|
|
||||||
|
const server = net.createServer(function(socket) {
|
||||||
|
console.log('Connection from', socket.remoteAddress, socket.remotePort);
|
||||||
|
var recvBuf = new Buffer(0);
|
||||||
|
socket.on('data', function(buf) {
|
||||||
|
recvBuf = Buffer.concat([recvBuf, buf]);
|
||||||
|
let pkgData;
|
||||||
|
while(pkgData = tryReadPackage(recvBuf)) {
|
||||||
|
if (pkgData) {
|
||||||
|
handlePackage(pkgData, socket);
|
||||||
|
var oldBuf = recvBuf;
|
||||||
|
recvBuf = new Buffer(recvBuf.length - pkgData.size - 4);
|
||||||
|
oldBuf.copy(recvBuf, 0, 4 + pkgData.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.on('end', function() {
|
||||||
|
console.log('Connection closed');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
server.listen(1113);
|
||||||
|
|
||||||
|
function tryReadPackage(buf) {
|
||||||
|
if (buf.length < 4) return;
|
||||||
|
const size = buf.readInt32LE(0);
|
||||||
|
if (buf.length < (4 + size)) return;
|
||||||
|
const pkg = {
|
||||||
|
size: size,
|
||||||
|
data: new Buffer(size)
|
||||||
|
};
|
||||||
|
buf.copy(pkg.data, 0, 4, 4 + size);
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePackage(pkg, socket) {
|
||||||
|
const bs = createBufferSegment(pkg.data);
|
||||||
|
const tcpPkg = TcpPackage.fromBufferSegment(bs);
|
||||||
|
console.log('Received TcpPackage (cmd=%s, correlationId=%s, size=%d)', TcpCommand.getName(tcpPkg.command), tcpPkg.correlationId, tcpPkg.data.count);
|
||||||
|
switch(tcpPkg.command) {
|
||||||
|
case TcpCommand.IdentifyClient: {
|
||||||
|
const dto = new ClientMessages.ClientIdentified({});
|
||||||
|
const buf = dto.constructor.encode(dto).finish();
|
||||||
|
const sendPkg = new TcpPackage(TcpCommand.ClientIdentified, TcpFlags.None, tcpPkg.correlationId, null, null, createBufferSegment(buf));
|
||||||
|
const sendBuf = sendPkg.asBuffer();
|
||||||
|
const sizeBuf = new Buffer(4);
|
||||||
|
sizeBuf.writeInt32LE(sendBuf.length, 0);
|
||||||
|
socket.write(sizeBuf);
|
||||||
|
socket.write(sendBuf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TcpCommand.HeartbeatRequestCommand: {
|
||||||
|
const sendPkg = new TcpPackage(TcpCommand.HeartbeatResponseCommand, TcpFlags.None, tcpPkg.correlationId, null, null);
|
||||||
|
const sendBuf = sendPkg.asBuffer();
|
||||||
|
const sizeBuf = new Buffer(4);
|
||||||
|
sizeBuf.writeInt32LE(sendBuf.length, 0);
|
||||||
|
socket.write(sizeBuf);
|
||||||
|
socket.write(sendBuf);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
console.log('Unhandled TcpPackage command \'%s\'', TcpCommand.getName(tcpPkg.command));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
|
mode: "none",
|
||||||
entry: "./src/client.js",
|
entry: "./src/client.js",
|
||||||
target: "node",
|
target: "node",
|
||||||
output: {
|
output: {
|
||||||
filename: "./lib/dist.js",
|
path: process.env.PWD + "/lib",
|
||||||
|
filename: "dist.js",
|
||||||
libraryTarget: "commonjs2"
|
libraryTarget: "commonjs2"
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
|
Reference in New Issue
Block a user