elasticio-sailor-nodejs 3.0.0-dev2 → 3.0.0-dev4

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/config/local.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "LOG_LEVEL": "trace",
3
- "ELASTICIO_FLOW_ID": "692ee23d4ab5d34bb7321559",
3
+ "ELASTICIO_FLOW_ID": "697b56aca8e2e9b35e1952da",
4
4
  "ELASTICIO_EXEC_ID": "exec_67890",
5
5
  "ELASTICIO_STEP_ID": "step_1",
6
6
  "ELASTICIO_CONTAINER_ID": "container_abcde",
@@ -9,11 +9,11 @@
9
9
  "ELASTICIO_COMP_ID": "component_3344",
10
10
  "ELASTICIO_FUNCTION": "data_trigger",
11
11
  "ELASTICIO_API_URI": "http://localhost:9000",
12
- "ELASTICIO_API_USERNAME": "task-692ee23d4ab5d34bb7321559",
13
- "ELASTICIO_API_KEY": "976ffec8-455b-494e-9478-2d66761f4040",
12
+ "ELASTICIO_API_USERNAME": "task-697b56aca8e2e9b35e1952da",
13
+ "ELASTICIO_API_KEY": "9b2e0465-b00c-4b57-af91-bfc3a830306c",
14
14
  "ELASTICIO_MESSAGE_CRYPTO_IV": "0.03091345790184",
15
15
  "ELASTICIO_MESSAGE_CRYPTO_PASSWORD": "password",
16
- "ELASTICIO_SAILOR_PROXY_URI": "http://localhost:5001",
16
+ "ELASTICIO_SAILOR_PROXY_URI": "http://localhost:4001",
17
17
  "ELASTICIO_COMPONENT_PATH": "./spec/component",
18
18
  "ELASTICIO_EMIT_LIGHTWEIGHT_MESSAGE": "true"
19
19
  }
@@ -262,34 +262,74 @@ class ProxyClient {
262
262
 
263
263
  logger.info('Going to fetch message body.', { objectId });
264
264
 
265
- try {
266
- const getObjectStream = this.clientSession.request({
267
- [HTTP2_HEADER_PATH]: `/object/${objectId}`,
268
- [HTTP2_HEADER_METHOD]: 'GET',
269
- [HTTP2_HEADER_AUTHORIZATION]: this.authHeader
270
- }).pipe(this._encryptor.createDecipher());
271
- // TODO: Add retries
272
- object = await new Promise((resolve, reject) => {
273
- const chunks = [];
274
- getObjectStream.on('data', chunk => {
275
- chunks.push(chunk);
276
- });
277
- getObjectStream.on('error', (err) => {
278
- logger.error(err, 'Error during fetching message body');
279
- reject(err);
280
- });
281
- getObjectStream.on('end', () => {
282
- logger.info('Message stream ended by server');
283
- const buffer = Buffer.concat(chunks);
284
- logger.info({ messageSize: buffer.length }, 'Received complete message from server');
285
- resolve({ data: JSON.parse(buffer.toString()) });
265
+ const maxRetries = this.settings.PROXY_OBJECT_REQUEST_RETRY_ATTEMPTS;
266
+ const retryDelay = this.settings.PROXY_OBJECT_REQUEST_RETRY_DELAY;
267
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
268
+ try {
269
+ object = await new Promise((resolve, reject) => {
270
+ const getObjectStream = this.clientSession.request({
271
+ [HTTP2_HEADER_PATH]: `/object/${objectId}`,
272
+ [HTTP2_HEADER_METHOD]: 'GET',
273
+ [HTTP2_HEADER_AUTHORIZATION]: this.authHeader
274
+ }).pipe(this._encryptor.createDecipher());
275
+
276
+ const chunks = [];
277
+ getObjectStream.on('data', chunk => {
278
+ chunks.push(chunk);
279
+ });
280
+ getObjectStream.on('error', (err) => {
281
+ logger.error(err, 'Error during fetching message body');
282
+ reject(err);
283
+ });
284
+ getObjectStream.on('end', () => {
285
+ logger.info('Message stream ended by server');
286
+ const buffer = Buffer.concat(chunks);
287
+ logger.info({ messageSize: buffer.length }, 'Received complete message from server');
288
+ resolve({ data: JSON.parse(buffer.toString()) });
289
+ });
286
290
  });
287
- });
288
- } catch (e) {
289
- log.error(e);
290
- throw new Error(`Failed to get message body with id=${objectId}`);
291
- }
292
291
 
292
+ if (attempt > 0) {
293
+ log.info({ attempt, maxRetries }, 'Fetch message body succeeded after retry');
294
+ }
295
+ return objectId;
296
+ } catch (error) {
297
+ const isLastAttempt = attempt === maxRetries;
298
+ const isRetryable = error.isNetworkError ||
299
+ (error.statusCode && error.statusCode >= 500) ||
300
+ error.code === 'ECONNRESET' ||
301
+ error.code === 'ETIMEDOUT';
302
+
303
+ if (!isRetryable || isLastAttempt) {
304
+ log.error({
305
+ attempt,
306
+ maxRetries,
307
+ error: error.message,
308
+ isRetryable
309
+ }, 'Fetch message body failed, no more retries');
310
+ throw error;
311
+ }
312
+
313
+ const delay = Math.min(
314
+ retryDelay * Math.pow(2, attempt),
315
+ this.settings.PROXY_OBJECT_REQUEST_MAX_RETRY_DELAY
316
+ );
317
+ log.warn({
318
+ attempt,
319
+ maxRetries,
320
+ error: error.message,
321
+ nextRetryIn: delay
322
+ }, 'Fetch message body failed, retrying...');
323
+
324
+ await new Promise(resolve => setTimeout(resolve, delay));
325
+
326
+ // Ensure connection is still valid before retry
327
+ if (!this.isConnected()) {
328
+ log.info('Reconnecting before retry...');
329
+ await this.connect();
330
+ }
331
+ }
332
+ }
293
333
  logger.info('Successfully obtained message body.', { objectId });
294
334
  logger.trace('Message body object received');
295
335
 
@@ -299,43 +339,101 @@ class ProxyClient {
299
339
  async uploadMessageBody(bodyBuf) {
300
340
  await this._ensureConnection();
301
341
 
302
- return new Promise((resolve, reject) => {
303
- // TODO: Add retries
304
- const postMessageStream = this.clientSession.request({
305
- [HTTP2_HEADER_PATH]: '/object',
306
- [HTTP2_HEADER_METHOD]: 'POST',
307
- [HTTP2_HEADER_AUTHORIZATION]: this.authHeader
308
- });
342
+ const maxRetries = this.settings.PROXY_OBJECT_REQUEST_RETRY_ATTEMPTS;
343
+ const retryDelay = this.settings.PROXY_OBJECT_REQUEST_RETRY_DELAY;
344
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
345
+ try {
346
+ const objectId = await new Promise((resolve, reject) => {
347
+ const postMessageStream = this.clientSession.request({
348
+ [HTTP2_HEADER_PATH]: '/object',
349
+ [HTTP2_HEADER_METHOD]: 'POST',
350
+ [HTTP2_HEADER_AUTHORIZATION]: this.authHeader
351
+ });
309
352
 
310
- postMessageStream.on('response', (headers, flags) => {
311
- const status = headers[http2.constants.HTTP2_HEADER_STATUS];
312
- if (status !== 200) {
313
- return reject(new Error(`Failed to upload message body, status code: ${status}`));
353
+ let responseData = '';
354
+ let statusCode = null;
355
+
356
+ postMessageStream.on('response', (headers, flags) => {
357
+ statusCode = headers[http2.constants.HTTP2_HEADER_STATUS];
358
+ if (statusCode !== 200) {
359
+ const error = new Error(`Failed to upload message body, status code: ${statusCode}`);
360
+ error.statusCode = statusCode;
361
+ return reject(error);
362
+ }
363
+ });
364
+
365
+ postMessageStream.on('data', chunk => {
366
+ responseData += chunk;
367
+ });
368
+
369
+ postMessageStream.on('error', (err) => {
370
+ log.error(err, 'Error during upload message body');
371
+ err.isNetworkError = true;
372
+ reject(err);
373
+ });
374
+
375
+ postMessageStream.on('end', () => {
376
+ if (!responseData) {
377
+ return
378
+ }
379
+ try {
380
+ const responseJson = JSON.parse(responseData);
381
+ resolve(responseJson.objectId);
382
+ } catch (e) {
383
+ log.error(e, 'Failed to parse upload message body response');
384
+ reject(e);
385
+ }
386
+ });
387
+
388
+ const cipher = this._encryptor.createCipher();
389
+ cipher.pipe(postMessageStream);
390
+ cipher.write(bodyBuf);
391
+ cipher.end();
392
+ });
393
+
394
+ // Success - return the objectId
395
+ if (attempt > 0) {
396
+ log.info({ attempt, maxRetries }, 'Upload message body succeeded after retry');
314
397
  }
315
- });
316
- let responseData = '';
317
- postMessageStream.on('data', chunk => {
318
- responseData += chunk;
319
- });
320
- postMessageStream.on('error', (err) => {
321
- log.error(err, 'Error during upload message body');
322
- reject(err);
323
- });
324
- postMessageStream.on('end', () => {
325
- try {
326
- const responseJson = JSON.parse(responseData);
327
- resolve(responseJson.objectId);
328
- } catch (e) {
329
- log.error(e, 'Failed to parse upload message body response');
330
- reject(e);
398
+ return objectId;
399
+
400
+ } catch (error) {
401
+ const isLastAttempt = attempt === maxRetries;
402
+ const isRetryable = error.isNetworkError ||
403
+ (error.statusCode && error.statusCode >= 500) ||
404
+ error.code === 'ECONNRESET' ||
405
+ error.code === 'ETIMEDOUT';
406
+
407
+ if (!isRetryable || isLastAttempt) {
408
+ log.error({
409
+ attempt,
410
+ maxRetries,
411
+ error: error.message,
412
+ isRetryable
413
+ }, 'Upload message body failed, no more retries');
414
+ throw error;
331
415
  }
332
- });
333
416
 
334
- const cipher = this._encryptor.createCipher();
335
- cipher.pipe(postMessageStream);
336
- cipher.write(bodyBuf);
337
- cipher.end();
338
- });
417
+ const delay = Math.min(
418
+ retryDelay * Math.pow(2, attempt),
419
+ this.settings.PROXY_OBJECT_REQUEST_MAX_RETRY_DELAY
420
+ );
421
+ log.warn({
422
+ attempt,
423
+ maxRetries,
424
+ error: error.message,
425
+ nextRetryIn: delay
426
+ }, 'Upload message body failed, retrying...');
427
+
428
+ await new Promise(resolve => setTimeout(resolve, delay));
429
+
430
+ // Ensure connection is still valid before retry
431
+ if (!this.isConnected()) {
432
+ log.info('Reconnecting before retry...');
433
+ await this.connect();
434
+ }
435
+ }
436
+ }
339
437
  }
340
438
 
341
439
  async listenForMessages(messageHandler) {
package/lib/sailor.js CHANGED
@@ -35,6 +35,8 @@ class Sailor {
35
35
  //eslint-disable-next-line new-cap
36
36
  this.apiClient = RestApiClient(
37
37
  settings.API_USERNAME,
38
+ // TODO find a way to make username and key consistent (without running api tests and looking up
39
+ // correct values in MongoDB)
38
40
  settings.API_KEY,
39
41
  {
40
42
  retryCount: settings.API_REQUEST_RETRY_ATTEMPTS,
package/lib/settings.js CHANGED
@@ -15,6 +15,9 @@ function getOptionalEnvVars(envVars) {
15
15
  PROXY_RECONNECT_MAX_DELAY: 30 * 1000, // 30 seconds
16
16
  PROXY_RECONNECT_BACKOFF_MULTIPLIER: 2,
17
17
  PROXY_RECONNECT_JITTER_FACTOR: 0.3,
18
+ PROXY_OBJECT_REQUEST_RETRY_ATTEMPTS: Infinity,
19
+ PROXY_OBJECT_REQUEST_RETRY_DELAY: 100,
20
+ PROXY_OBJECT_REQUEST_MAX_RETRY_DELAY: 5 * 60 * 1000, // 5 mins
18
21
 
19
22
  // TODO: Move to proxy?
20
23
  DATA_RATE_LIMIT: 10, // 10 data events every 100ms
@@ -28,6 +31,7 @@ function getOptionalEnvVars(envVars) {
28
31
  // Should be defaulted to true and moved to proxy
29
32
  AMQP_PERSISTENT_MESSAGES: false,
30
33
 
34
+ OBJECT_STORAGE_SIZE_THRESHOLD: 1048576,
31
35
  OUTGOING_MESSAGE_SIZE_LIMIT: 10485760,
32
36
  NO_SELF_PASSTRHOUGH: false,
33
37
  PROTOCOL_VERSION: 1,
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": "3.0.0-dev2",
4
+ "version": "3.0.0-dev4",
5
5
  "main": "run.js",
6
6
  "scripts": {
7
7
  "build": "tsc",