elasticio-sailor-nodejs 2.6.24 → 2.6.26-dev1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -1
- package/lib/amqp.js +77 -99
- package/lib/ipc.js +13 -0
- package/lib/sailor.js +2 -2
- package/package.json +8 -7
- package/run.js +45 -25
- package/runService.js +5 -1
- package/.travis.yml +0 -8
- package/Procfile +0 -2
- package/createQueues.js +0 -148
- package/gulpfile.js +0 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## 2.6.26 (June 24, 2021)
|
|
2
|
+
|
|
3
|
+
* Replaced exit on "unhandledRejection" with error logging
|
|
4
|
+
|
|
5
|
+
## 2.6.25 (June 17, 2021)
|
|
6
|
+
|
|
7
|
+
* Improve consume performance by switching back to pushing instead of polling, keeping reconnections mechanism
|
|
8
|
+
([#5432](https://github.com/elasticio/elasticio/issues/5432))
|
|
9
|
+
|
|
1
10
|
## 2.6.24 (January 27, 2021)
|
|
2
11
|
|
|
3
12
|
* Fix "Big messages processing is not stable" errors ([#5051](https://github.com/elasticio/elasticio/issues/5051))
|
|
@@ -23,7 +32,7 @@
|
|
|
23
32
|
* Separate connections for consuming and publishing
|
|
24
33
|
* Consuming is done with polling instead of pushing
|
|
25
34
|
* Reconnects on connection errors
|
|
26
|
-
*
|
|
35
|
+
* Handling consumer cancel notification
|
|
27
36
|
* Lowered log levels of some developers' log messages
|
|
28
37
|
* Addded env vars:
|
|
29
38
|
* AMQP_RECONNECT_ATTEMPTS - number of retries on connection close
|
package/lib/amqp.js
CHANGED
|
@@ -5,18 +5,17 @@ const Encryptor = require('./encryptor.js');
|
|
|
5
5
|
const _ = require('lodash');
|
|
6
6
|
const eventToPromise = require('event-to-promise');
|
|
7
7
|
const uuid = require('uuid');
|
|
8
|
+
const os = require('os');
|
|
8
9
|
|
|
9
10
|
const HEADER_ROUTING_KEY = 'x-eio-routing-key';
|
|
10
11
|
const HEADER_ERROR_RESPONSE = 'x-eio-error-response';
|
|
11
12
|
|
|
12
|
-
|
|
13
13
|
class Amqp {
|
|
14
14
|
constructor(settings) {
|
|
15
|
-
this.reconectCount = 0;
|
|
16
15
|
this.settings = settings;
|
|
17
16
|
this._encryptor = new Encryptor(this.settings.MESSAGE_CRYPTO_PASSWORD, this.settings.MESSAGE_CRYPTO_IV);
|
|
18
17
|
this.closed = true;
|
|
19
|
-
this.consume =
|
|
18
|
+
this.consume = undefined;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
async connect() {
|
|
@@ -25,7 +24,7 @@ class Amqp {
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
async disconnect() {
|
|
28
|
-
this.stopConsume();
|
|
27
|
+
await this.stopConsume();
|
|
29
28
|
this.closed = true;
|
|
30
29
|
log.trace('Close AMQP connections');
|
|
31
30
|
if (this._readConnection) {
|
|
@@ -53,7 +52,7 @@ class Amqp {
|
|
|
53
52
|
try {
|
|
54
53
|
await this._readConnection.close();
|
|
55
54
|
} catch (alreadyClosed) {
|
|
56
|
-
log.debug('AMQP
|
|
55
|
+
log.debug('AMQP read connection is closed already');
|
|
57
56
|
}
|
|
58
57
|
try {
|
|
59
58
|
await this._writeConnection.close();
|
|
@@ -73,12 +72,14 @@ class Amqp {
|
|
|
73
72
|
} while (!this._readConnection);
|
|
74
73
|
return this._readConnection;
|
|
75
74
|
}
|
|
75
|
+
log.debug('Creating new read connection');
|
|
76
76
|
this._creatingReadConnection = true;
|
|
77
|
-
this._readConnection = await this._createConnection();
|
|
77
|
+
this._readConnection = await this._createConnection('read');
|
|
78
78
|
this._creatingReadConnection = false;
|
|
79
|
+
log.debug('Read connection created');
|
|
79
80
|
this._readConnection.on('error', err => log.error({ err }, 'AMQP read Connection error'));
|
|
80
81
|
this._readConnection.once('close', err => {
|
|
81
|
-
log.error({ err }, '
|
|
82
|
+
log.error({ err }, 'Unexpected connection close.');
|
|
82
83
|
delete this._readConnection;
|
|
83
84
|
});
|
|
84
85
|
return this._readConnection;
|
|
@@ -94,12 +95,14 @@ class Amqp {
|
|
|
94
95
|
} while (!this._writeConnection);
|
|
95
96
|
return this._writeConnection;
|
|
96
97
|
}
|
|
98
|
+
log.debug('Creating new write connection');
|
|
97
99
|
this._creatingWriteConnection = true;
|
|
98
|
-
this._writeConnection = await this._createConnection();
|
|
100
|
+
this._writeConnection = await this._createConnection('write');
|
|
99
101
|
this._creatingWriteConnection = false;
|
|
102
|
+
log.debug('Write connection created');
|
|
100
103
|
this._writeConnection.on('error', err => log.error({ err }, 'AMQP write Connection error'));
|
|
101
104
|
this._writeConnection.once('close', err => {
|
|
102
|
-
log.error({ err }, '
|
|
105
|
+
log.error({ err }, 'Unexpected connection close.');
|
|
103
106
|
delete this._writeConnection;
|
|
104
107
|
});
|
|
105
108
|
return this._writeConnection;
|
|
@@ -115,14 +118,24 @@ class Amqp {
|
|
|
115
118
|
} while (!this.consumerChannel);
|
|
116
119
|
return this.consumerChannel;
|
|
117
120
|
}
|
|
121
|
+
log.debug({ prefetch: this.settings.RABBITMQ_PREFETCH_SAILOR }, 'Creating new consume channel');
|
|
118
122
|
this._creatingConsumerChannel = true;
|
|
119
123
|
const amqp = await this._ensureReadConnection();
|
|
120
124
|
this.consumerChannel = await amqp.createChannel();
|
|
125
|
+
log.debug('Consume channel created');
|
|
121
126
|
this._creatingConsumerChannel = false;
|
|
127
|
+
this.consumerChannel.prefetch(this.settings.RABBITMQ_PREFETCH_SAILOR);
|
|
122
128
|
this.consumerChannel.on('error', err => log.error({ err }, 'Consumer channel error'));
|
|
123
129
|
this.consumerChannel.once('close', () => {
|
|
124
|
-
this.consumerChannel.closed = true;
|
|
125
130
|
delete this.consumerChannel;
|
|
131
|
+
if (this.consume) {
|
|
132
|
+
log.warn('Channel unexpectedly closed, but we were listening. Reconnecting and re-listening queue');
|
|
133
|
+
this.consume.consumerTag = undefined;
|
|
134
|
+
// when RabbitMQ closes connection, amqplib will first emit 'close' for channel and then for connection
|
|
135
|
+
// we use setImmediate to wait for connection 'close' event which will unset connection property
|
|
136
|
+
// otherwise listenQueue will try to create channel on closing connection and fail hard
|
|
137
|
+
setImmediate(() => this.listenQueue(this.consume.queue, this.consume.messageHandler));
|
|
138
|
+
}
|
|
126
139
|
});
|
|
127
140
|
return this.consumerChannel;
|
|
128
141
|
}
|
|
@@ -137,11 +150,12 @@ class Amqp {
|
|
|
137
150
|
} while (!this.publishChannel);
|
|
138
151
|
return this.publishChannel;
|
|
139
152
|
}
|
|
153
|
+
log.debug('Creating new publish connection and channel');
|
|
140
154
|
this._creatingPublishChannel = true;
|
|
141
155
|
const amqp = await this._ensureWriteConnection();
|
|
142
156
|
this.publishChannel = await amqp.createConfirmChannel();
|
|
157
|
+
log.debug('Publish connection and channel created');
|
|
143
158
|
this._creatingPublishChannel = false;
|
|
144
|
-
this.publishChannel.prefetch(this.settings.RABBITMQ_PREFETCH_SAILOR);
|
|
145
159
|
this.publishChannel.on('error', err => log.error({ err }, 'Publish channel error'));
|
|
146
160
|
this.publishChannel.once('close', () => {
|
|
147
161
|
delete this.publishChannel;
|
|
@@ -149,104 +163,68 @@ class Amqp {
|
|
|
149
163
|
return this.publishChannel;
|
|
150
164
|
}
|
|
151
165
|
|
|
152
|
-
async
|
|
153
|
-
let channel;
|
|
154
|
-
|
|
155
|
-
while (!this.closed) {
|
|
156
|
-
channel = await this._ensureConsumerChannel();
|
|
157
|
-
let msg;
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
msg = await channel.get(queue);
|
|
161
|
-
} catch (e) {
|
|
162
|
-
if (this.closed) {
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
if (channel.closed) {
|
|
166
|
-
if (e.isOperational && e.fields && e.fields.replyCode === 404) { // deleted queue
|
|
167
|
-
throw e;
|
|
168
|
-
}
|
|
169
|
-
delete this.consumerChannel;
|
|
170
|
-
} else {
|
|
171
|
-
throw e;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (msg) {
|
|
175
|
-
yield msg;
|
|
176
|
-
} else {
|
|
177
|
-
// need it here since if there's no message channel.get seems to be not true async
|
|
178
|
-
// and event loop is being blocked without this
|
|
179
|
-
// also to avoid CPU load if no queue is empty
|
|
180
|
-
await new Promise(resolve => setTimeout(resolve, parseInt(this.settings.WAIT_MESSAGES_TIMEOUT)));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async _createConnection() {
|
|
166
|
+
async _createConnection(name) {
|
|
186
167
|
const uri = this.settings.AMQP_URI;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
168
|
+
const allowedAttempts = parseInt(this.settings.AMQP_RECONNECT_ATTEMPTS);
|
|
169
|
+
let lastErr;
|
|
170
|
+
let attempts = 0;
|
|
171
|
+
while (attempts <= allowedAttempts) {
|
|
172
|
+
if (attempts > 0) {
|
|
173
|
+
await new Promise(resolve => setTimeout(resolve, parseInt(this.settings.AMQP_RECONNECT_TIMEOUT)));
|
|
174
|
+
log.debug(
|
|
175
|
+
{
|
|
176
|
+
reconnectAttempt: attempts,
|
|
177
|
+
AMQP_RECONNECT_ATTEMPTS: this.settings.AMQP_RECONNECT_ATTEMPTS
|
|
178
|
+
},
|
|
179
|
+
'AMQP Reconnecting'
|
|
180
|
+
);
|
|
181
|
+
}
|
|
190
182
|
try {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
log.debug(
|
|
198
|
-
{
|
|
199
|
-
reconnectCount: this.reconectCount,
|
|
200
|
-
AMQP_RECONNECT_ATTEMPTS: this.settings.AMQP_RECONNECT_ATTEMPTS
|
|
201
|
-
},
|
|
202
|
-
'reconnecting'
|
|
203
|
-
);
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
throw e;
|
|
183
|
+
return await amqplib.connect(uri,
|
|
184
|
+
{ clientProperties: { connection_name: `${os.hostname()}-${name}` } });
|
|
185
|
+
} catch (err) {
|
|
186
|
+
lastErr = err;
|
|
187
|
+
log.error(err, 'AMQP Connection error');
|
|
188
|
+
attempts++;
|
|
207
189
|
}
|
|
208
|
-
break;
|
|
209
190
|
}
|
|
210
|
-
|
|
191
|
+
throw lastErr;
|
|
211
192
|
}
|
|
212
193
|
|
|
213
|
-
stopConsume() {
|
|
214
|
-
this.consume
|
|
194
|
+
async stopConsume() {
|
|
195
|
+
if (!this.consume) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const consume = this.consume;
|
|
199
|
+
this.consume = undefined;
|
|
200
|
+
await this.consumerChannel.cancel(consume.consumerTag);
|
|
201
|
+
log.debug({ queue: consume.queue }, 'Stopped listening for messages');
|
|
215
202
|
}
|
|
216
203
|
|
|
217
204
|
async listenQueue(queue, messageHandler) {
|
|
218
|
-
|
|
219
|
-
this.consume = true;
|
|
205
|
+
await this._ensureConsumerChannel();
|
|
220
206
|
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
amqpMessage.fields.deliveryTag
|
|
233
|
-
);
|
|
234
|
-
this.reject(amqpMessage);
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
try {
|
|
238
|
-
await messageHandler(message, amqpMessage);
|
|
239
|
-
} catch (e) {
|
|
240
|
-
log.error(e, 'Failed to process message #%j, reject', amqpMessage.fields.deliveryTag);
|
|
241
|
-
this.reject(amqpMessage);
|
|
242
|
-
}
|
|
207
|
+
const { consumerTag } = await this.consumerChannel.consume(queue, async (amqpMessage) => {
|
|
208
|
+
let message;
|
|
209
|
+
try {
|
|
210
|
+
message = this._decodeMessage(amqpMessage);
|
|
211
|
+
} catch (e) {
|
|
212
|
+
log.error(e,
|
|
213
|
+
'Error occurred while parsing message #%j payload',
|
|
214
|
+
amqpMessage.fields.deliveryTag
|
|
215
|
+
);
|
|
216
|
+
this.reject(amqpMessage);
|
|
217
|
+
return;
|
|
243
218
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
219
|
+
try {
|
|
220
|
+
await messageHandler(message, amqpMessage);
|
|
221
|
+
} catch (e) {
|
|
222
|
+
log.error(e, 'Failed to process message #%j, reject', amqpMessage.fields.deliveryTag);
|
|
223
|
+
this.reject(amqpMessage);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
log.debug({ queue }, 'Started listening for messages');
|
|
227
|
+
this.consume = { queue, messageHandler, consumerTag };
|
|
250
228
|
}
|
|
251
229
|
|
|
252
230
|
_decodeMessage(amqpMessage) {
|
|
@@ -337,7 +315,7 @@ class Amqp {
|
|
|
337
315
|
|
|
338
316
|
async sendToExchange(exchangeName, routingKey, payload, options, throttle) {
|
|
339
317
|
if (throttle) {
|
|
340
|
-
log.debug('
|
|
318
|
+
log.debug('Throttling outgoing message');
|
|
341
319
|
await throttle();
|
|
342
320
|
}
|
|
343
321
|
const buffer = Buffer.from(payload);
|
package/lib/ipc.js
ADDED
package/lib/sailor.js
CHANGED
|
@@ -209,13 +209,13 @@ class Sailor {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
scheduleShutdown() {
|
|
212
|
+
async scheduleShutdown() {
|
|
213
213
|
if (this.shutdownCallback) {
|
|
214
214
|
log.debug('scheduleShutdown – shutdown is already scheduled, do nothing');
|
|
215
215
|
return new Promise(resolve => this.shutdownCallback = resolve);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
this.amqpConnection.stopConsume();
|
|
218
|
+
await this.amqpConnection.stopConsume();
|
|
219
219
|
if (this.messagesCount === 0) {
|
|
220
220
|
// there is no unfinished processMessage invocation, let's just resolve scheduleShutdown now
|
|
221
221
|
log.debug('scheduleShutdown – about to shutdown immediately');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elasticio-sailor-nodejs",
|
|
3
3
|
"description": "The official elastic.io library for bootstrapping and executing for Node.js connectors",
|
|
4
|
-
"version": "2.6.
|
|
4
|
+
"version": "2.6.26-dev1",
|
|
5
5
|
"main": "run.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "./node_modules/.bin/eslint lib spec mocha_spec lib run.js runService.js",
|
|
@@ -15,21 +15,22 @@
|
|
|
15
15
|
"node": ">=12.13.0"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@elastic.io/object-storage-client": "1.0.
|
|
19
|
-
"amqplib": "0.
|
|
18
|
+
"@elastic.io/object-storage-client": "1.0.1",
|
|
19
|
+
"amqplib": "0.8.0",
|
|
20
20
|
"bunyan": "1.8.10",
|
|
21
21
|
"co": "4.6.0",
|
|
22
22
|
"debug": "3.1.0",
|
|
23
|
-
"elasticio-rest-node": "1.2.
|
|
23
|
+
"elasticio-rest-node": "1.2.7",
|
|
24
24
|
"event-to-promise": "0.8.0",
|
|
25
|
-
"lodash": "4.17.
|
|
25
|
+
"lodash": "4.17.21",
|
|
26
26
|
"p-throttle": "2.1.0",
|
|
27
|
-
"q": "1.
|
|
27
|
+
"q": "1.5.1",
|
|
28
28
|
"request-promise-native": "1.0.5",
|
|
29
|
-
"requestretry": "
|
|
29
|
+
"requestretry": "7.0.2",
|
|
30
30
|
"uuid": "3.0.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
+
"rabbitmq-stats": "1.2.3",
|
|
33
34
|
"chai": "4.2.0",
|
|
34
35
|
"del": "2.2.2",
|
|
35
36
|
"eslint": "4.19.1",
|
package/run.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entrypoint for starting task step.
|
|
3
|
+
*/
|
|
1
4
|
const logger = require('./lib/logging.js');
|
|
2
5
|
const Sailor = require('./lib/sailor.js').Sailor;
|
|
3
6
|
const settings = require('./lib/settings.js');
|
|
4
|
-
const
|
|
7
|
+
const { IPC } = require('./lib/ipc.js');
|
|
8
|
+
const Q = require('q');
|
|
5
9
|
const http = require('http');
|
|
6
10
|
|
|
7
11
|
let sailor;
|
|
12
|
+
let sailorInit;
|
|
8
13
|
let disconnectRequired;
|
|
9
14
|
|
|
10
15
|
// miserable try to workaround issue described in https://github.com/elasticio/elasticio/issues/4874
|
|
@@ -13,7 +18,10 @@ http.globalAgent = new Agent({
|
|
|
13
18
|
keepAlive: true
|
|
14
19
|
});
|
|
15
20
|
|
|
16
|
-
async function putOutToSea(settings) {
|
|
21
|
+
async function putOutToSea(settings, ipc) {
|
|
22
|
+
ipc.send('init:started');
|
|
23
|
+
const deferred = Q.defer();
|
|
24
|
+
sailorInit = deferred.promise;
|
|
17
25
|
sailor = new Sailor(settings);
|
|
18
26
|
|
|
19
27
|
//eslint-disable-next-line no-extra-boolean-cast
|
|
@@ -38,36 +46,28 @@ async function putOutToSea(settings) {
|
|
|
38
46
|
|
|
39
47
|
await sailor.runHookInit();
|
|
40
48
|
await sailor.run();
|
|
49
|
+
deferred.resolve();
|
|
50
|
+
ipc.send('init:ended');
|
|
41
51
|
}
|
|
42
52
|
|
|
43
|
-
function disconnectAndExit() {
|
|
53
|
+
async function disconnectAndExit() {
|
|
44
54
|
if (!disconnectRequired) {
|
|
45
55
|
return;
|
|
46
56
|
}
|
|
47
57
|
disconnectRequired = false;
|
|
48
|
-
|
|
58
|
+
|
|
59
|
+
try {
|
|
49
60
|
logger.info('Disconnecting...');
|
|
50
|
-
|
|
61
|
+
await sailor.disconnect();
|
|
51
62
|
logger.info('Successfully disconnected');
|
|
52
63
|
process.exit();
|
|
53
|
-
}
|
|
54
|
-
logger.error('Unable to disconnect'
|
|
64
|
+
} catch (err) {
|
|
65
|
+
logger.error(err, 'Unable to disconnect');
|
|
55
66
|
process.exit(-1);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function _disconnectOnly() {
|
|
60
|
-
if (!disconnectRequired) {
|
|
61
|
-
return Promise.resolve();
|
|
62
67
|
}
|
|
63
|
-
return sailor.disconnect();
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
function
|
|
67
|
-
return sailor.amqpConnection.consumerChannel.close();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function gracefulShutdown() {
|
|
70
|
+
async function gracefulShutdown() {
|
|
71
71
|
if (!disconnectRequired) {
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
@@ -77,12 +77,20 @@ function gracefulShutdown() {
|
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
// we connect to amqp, create channels, start listen a queue on init and interrupting this process with 'disconnect'
|
|
81
|
+
// will lead to undefined behaviour
|
|
82
|
+
logger.trace('Checking/waiting for init before graceful shutdown');
|
|
83
|
+
await sailorInit;
|
|
84
|
+
logger.trace('Waited an init before graceful shutdown');
|
|
85
|
+
|
|
86
|
+
await sailor.scheduleShutdown();
|
|
87
|
+
await disconnectAndExit();
|
|
81
88
|
}
|
|
82
89
|
|
|
83
|
-
async function run(settings) {
|
|
90
|
+
async function run(settings, ipc) {
|
|
84
91
|
try {
|
|
85
|
-
await putOutToSea(settings);
|
|
92
|
+
await putOutToSea(settings, ipc);
|
|
93
|
+
logger.info('Fully initialized and waiting for messages');
|
|
86
94
|
} catch (e) {
|
|
87
95
|
if (sailor && !sailor.amqpConnection.closed) {
|
|
88
96
|
await sailor.reportError(e);
|
|
@@ -91,8 +99,17 @@ async function run(settings) {
|
|
|
91
99
|
}
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
exports.
|
|
95
|
-
|
|
102
|
+
exports.__test__ = {
|
|
103
|
+
disconnectOnly: function disconnectOnly() {
|
|
104
|
+
if (!disconnectRequired) {
|
|
105
|
+
return Promise.resolve();
|
|
106
|
+
}
|
|
107
|
+
return sailor.disconnect();
|
|
108
|
+
},
|
|
109
|
+
closeConsumerChannel: function closeConsumerChannel() {
|
|
110
|
+
return sailor.amqpConnection.consumerChannel.close();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
96
113
|
exports.run = run;
|
|
97
114
|
exports.putOutToSea = putOutToSea;
|
|
98
115
|
|
|
@@ -108,6 +125,9 @@ if (require.main === module || process.mainModule.filename === __filename) {
|
|
|
108
125
|
});
|
|
109
126
|
|
|
110
127
|
process.on('uncaughtException', logger.criticalErrorAndExit.bind(logger, 'process.uncaughtException'));
|
|
128
|
+
process.on('unhandledRejection', (err) => logger.error(err, 'process.unhandledRejection'));
|
|
129
|
+
|
|
130
|
+
const ipc = new IPC();
|
|
111
131
|
|
|
112
|
-
run(settings.readFrom(process.env));
|
|
132
|
+
run(settings.readFrom(process.env), ipc);
|
|
113
133
|
}
|
package/runService.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entrypoint for starting service which will handle verifyCredentials and selectModel requests.
|
|
3
|
+
*/
|
|
1
4
|
const logger = require('./lib/logging');
|
|
2
5
|
const service = require('./lib/service');
|
|
3
6
|
const debug = require('debug')('sailor');
|
|
@@ -12,4 +15,5 @@ service.processService(serviceMethod, process.env)
|
|
|
12
15
|
process.exit(0);
|
|
13
16
|
});
|
|
14
17
|
|
|
15
|
-
process.on('uncaughtException', logger.criticalErrorAndExit.bind(logger, '
|
|
18
|
+
process.on('uncaughtException', logger.criticalErrorAndExit.bind(logger, 'process.uncaughtException'));
|
|
19
|
+
process.on('unhandledRejection', (err) => logger.error(err, 'process.unhandledRejection'));
|
package/.travis.yml
DELETED
package/Procfile
DELETED
package/createQueues.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
var amqp = require('./lib/amqp.js');
|
|
2
|
-
var settings = require('./lib/settings.js').readFrom(process.env);
|
|
3
|
-
var Q = require('q');
|
|
4
|
-
var util = require('util');
|
|
5
|
-
|
|
6
|
-
var execId = "1432205514864";
|
|
7
|
-
|
|
8
|
-
var task = {
|
|
9
|
-
"id" : "5559edd38968ec0736000003",
|
|
10
|
-
"user" : "5527f0ea43238e5d5f000001",
|
|
11
|
-
"data" : {
|
|
12
|
-
"step_2" : {
|
|
13
|
-
"_account" : "554b53aed5178d6540000001"
|
|
14
|
-
},
|
|
15
|
-
"step_3" : {
|
|
16
|
-
"mapper" : {
|
|
17
|
-
"data" : {
|
|
18
|
-
"qty" : "2"
|
|
19
|
-
},
|
|
20
|
-
"product" : "Btestsku"
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
"step_1" : {
|
|
24
|
-
"interval" : "minute",
|
|
25
|
-
"_account" : "5559ed6b8968ec0736000002"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"recipe" : {
|
|
29
|
-
"nodes" : [
|
|
30
|
-
{
|
|
31
|
-
"first" : true,
|
|
32
|
-
"id" : "step_1",
|
|
33
|
-
"function" : "getProducts",
|
|
34
|
-
"compId" : "shopware"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"id" : "step_3",
|
|
38
|
-
"function" : "map",
|
|
39
|
-
"compId" : "mapper"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"id" : "step_2",
|
|
43
|
-
"function" : "updateInventory",
|
|
44
|
-
"compId" : "magento"
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
"connections" : [
|
|
48
|
-
{
|
|
49
|
-
"to" : "step_3",
|
|
50
|
-
"from" : "step_1"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"to" : "step_2",
|
|
54
|
-
"from" : "step_3"
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
var amqpConnection = new amqp.AMQPConnection(settings);
|
|
62
|
-
amqpConnection.connect(settings.AMQP_URI).then(function(){
|
|
63
|
-
сreateQueuesAndExchanges(execId, task, "step_1", "step_2");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
function сreateQueuesAndExchanges(execId, task, stepId, nextStepId){
|
|
67
|
-
|
|
68
|
-
var EXCHANGE_NAME = 'userexchange:' + task.user;
|
|
69
|
-
|
|
70
|
-
var MESSAGE_TAG = util.format('%s:%s:%s:message', task.id, stepId, execId);
|
|
71
|
-
var ERROR_TAG = util.format('%s:%s:%s:error', task.id, stepId, execId);
|
|
72
|
-
var REBOUND_TAG = util.format('%s:%s:%s:rebound', task.id, stepId, execId);
|
|
73
|
-
|
|
74
|
-
var MESSAGES_QUEUE = util.format('%s:%s:%s:messages', task.id, stepId, execId);
|
|
75
|
-
var MESSAGES_LISTENING_QUEUE = util.format('%s:%s:%s:messages', task.id, nextStepId, execId);
|
|
76
|
-
var REBOUNDS_QUEUE = util.format('%s:%s:%s:rebounds', task.id, stepId, execId);
|
|
77
|
-
|
|
78
|
-
console.log('INCOMING_MESSAGES_QUEUE=%s', MESSAGES_QUEUE);
|
|
79
|
-
console.log('EXCHANGE_NAME=%s', EXCHANGE_NAME);
|
|
80
|
-
console.log('MESSAGE_TAG=%s', MESSAGE_TAG);
|
|
81
|
-
console.log('ERROR_TAG=%s', ERROR_TAG);
|
|
82
|
-
console.log('REBOUND_TAG=%s', REBOUND_TAG);
|
|
83
|
-
console.log('MESSAGES_QUEUE=%s', MESSAGES_QUEUE);
|
|
84
|
-
console.log('REBOUNDS_QUEUE=%s', REBOUNDS_QUEUE);
|
|
85
|
-
|
|
86
|
-
var userExchange = {
|
|
87
|
-
name: EXCHANGE_NAME,
|
|
88
|
-
type: 'direct',
|
|
89
|
-
options: {
|
|
90
|
-
durable: true,
|
|
91
|
-
autoDelete: false
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
var messagesQueue = {
|
|
96
|
-
name: MESSAGES_QUEUE,
|
|
97
|
-
options: {
|
|
98
|
-
durable: true,
|
|
99
|
-
autoDelete: false
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
var messagesListeningQueue = {
|
|
104
|
-
name: MESSAGES_LISTENING_QUEUE,
|
|
105
|
-
options: {
|
|
106
|
-
durable: true,
|
|
107
|
-
autoDelete: false
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
var REBOUND_QUEUE_TTL = 10 * 60 * 1000; // 10 min
|
|
112
|
-
|
|
113
|
-
var reboundsQueue = {
|
|
114
|
-
name: REBOUNDS_QUEUE,
|
|
115
|
-
options: {
|
|
116
|
-
durable: true,
|
|
117
|
-
autoDelete: false,
|
|
118
|
-
arguments: {
|
|
119
|
-
'x-message-ttl': REBOUND_QUEUE_TTL,
|
|
120
|
-
'x-dead-letter-exchange': EXCHANGE_NAME, // send dead rebounded queues back to exchange
|
|
121
|
-
'x-dead-letter-routing-key': MESSAGE_TAG // with tag as message
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
return Q.all([
|
|
127
|
-
assertExchange(amqpConnection.publishChannel, userExchange), // check that exchange exists
|
|
128
|
-
assertQueue(amqpConnection.publishChannel, messagesQueue), // create messages queue
|
|
129
|
-
assertQueue(amqpConnection.publishChannel, messagesListeningQueue), // create messages queue
|
|
130
|
-
amqpConnection.publishChannel.bindQueue(messagesListeningQueue.name, userExchange.name, MESSAGE_TAG),
|
|
131
|
-
assertQueue(amqpConnection.publishChannel, reboundsQueue), // create rebounds queue
|
|
132
|
-
amqpConnection.publishChannel.bindQueue(reboundsQueue.name, userExchange.name, REBOUND_TAG)
|
|
133
|
-
]).then(function(){
|
|
134
|
-
console.log('Successfully asserted all queues');
|
|
135
|
-
}).done();
|
|
136
|
-
|
|
137
|
-
function assertQueue(channel, queue) {
|
|
138
|
-
return channel.assertQueue(queue.name, queue.options).then(function assertQueueSuccess() {
|
|
139
|
-
console.log('Succesfully asserted queue: ' + queue.name);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function assertExchange(channel, exchange) {
|
|
144
|
-
return channel.assertExchange(exchange.name, exchange.type, exchange.options).then(function assertExchangeSuccess() {
|
|
145
|
-
console.log('Succesfully asserted exchange: ' + exchange.name);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
}
|
package/gulpfile.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
var gulp = require('gulp');
|
|
2
|
-
var jasmine = require('gulp-jasmine');
|
|
3
|
-
var istanbul = require('gulp-istanbul');
|
|
4
|
-
var del = require('del');
|
|
5
|
-
|
|
6
|
-
var paths = {
|
|
7
|
-
code: ['./lib/*.js'],
|
|
8
|
-
spec: ['./spec/**/*.spec.js'],
|
|
9
|
-
coverageReport: './coverage/lcov.info',
|
|
10
|
-
coverage: './coverage'
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
gulp.task('clean:coverage', function (cb) {
|
|
14
|
-
del([paths.coverage], cb);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
gulp.task('istanbul', function (cb) {
|
|
18
|
-
gulp
|
|
19
|
-
.src(paths.code)
|
|
20
|
-
.pipe(istanbul()) // Covering files
|
|
21
|
-
.pipe(istanbul.hookRequire()) // Force `require` to return covered files
|
|
22
|
-
.on('finish', function () {
|
|
23
|
-
gulp
|
|
24
|
-
.src(paths.spec)
|
|
25
|
-
.pipe(jasmine())
|
|
26
|
-
.pipe(istanbul.writeReports()) // Creating the reports after tests runned
|
|
27
|
-
.on('end', cb);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
gulp.task('coverage', ['clean:coverage', 'istanbul']);
|