@terascope/elasticsearch-api 3.3.1 → 3.3.3

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/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ /* eslint-disable camelcase */
2
+
1
3
  'use strict';
2
4
 
3
5
  // polyfill because opensearch has references to an api that won't exist
@@ -6,31 +8,15 @@ require('setimmediate');
6
8
 
7
9
  const Promise = require('bluebird');
8
10
  const {
9
- isTest,
10
- TSError,
11
- isFatalError,
12
- parseError,
13
- getBackoffDelay,
14
- isRetryableError,
15
- get,
16
- toNumber,
17
- isString,
18
- isSimpleObject,
19
- castArray,
20
- flatten,
21
- toBoolean,
22
- uniq,
23
- random,
24
- cloneDeep,
25
- DataEntity,
26
- isDeepEqual,
27
- getTypeOf,
28
- isProd
11
+ isTest, TSError, isFatalError,
12
+ parseError, getBackoffDelay, isRetryableError,
13
+ get, toNumber, isString, isSimpleObject,
14
+ castArray, flatten, toBoolean,
15
+ uniq, random, cloneDeep, DataEntity,
16
+ isDeepEqual, getTypeOf, isProd
29
17
  } = require('@terascope/utils');
30
18
  const { ElasticsearchDistribution } = require('@terascope/types');
31
19
 
32
- const { inspect } = require('util');
33
-
34
20
  const DOCUMENT_EXISTS = 409;
35
21
  const TOO_MANY_REQUESTS = 429;
36
22
 
@@ -55,9 +41,13 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
55
41
 
56
42
  const { connection = 'unknown' } = config;
57
43
 
58
- function count(query) {
44
+ async function count(query) {
59
45
  query.size = 0;
60
- return _searchES(query).then((data) => get(data, 'hits.total.value', get(data, 'hits.total')));
46
+ const response = await _searchES(query);
47
+
48
+ const data = get(response, 'hits.total.value', get(response, 'hits.total'));
49
+
50
+ return data;
61
51
  }
62
52
 
63
53
  function convertDocToDataEntity(doc) {
@@ -80,18 +70,24 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
80
70
  }
81
71
 
82
72
  function search(query) {
83
- if (!isElasticsearch6()) {
84
- if (query._sourceExclude) {
85
- query._sourceExcludes = query._sourceExclude.slice();
86
- delete query._sourceExclude;
87
- }
88
- if (query._sourceInclude) {
89
- query._sourceIncludes = query._sourceInclude.slice();
90
- delete query._sourceInclude;
91
- }
73
+ const {
74
+ _sourceInclude, _source_includes,
75
+ _sourceExclude, _source_excludes,
76
+ ...safeQuery
77
+ } = query;
78
+
79
+ const sourceIncludes = _sourceInclude || _source_includes;
80
+ const sourceExcludes = _sourceExclude || _source_excludes;
81
+
82
+ if (sourceIncludes) {
83
+ safeQuery._source_includes = sourceIncludes;
84
+ }
85
+
86
+ if (sourceExcludes) {
87
+ safeQuery._source_excludes = sourceExcludes;
92
88
  }
93
89
 
94
- return _searchES(query).then((data) => {
90
+ return _searchES(safeQuery).then((data) => {
95
91
  if (config.full_response) {
96
92
  return data;
97
93
  }
@@ -342,7 +338,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
342
338
  if (record.action == null) {
343
339
  let dbg = '';
344
340
  if (!isProd) {
345
- dbg = `, dbg: ${inspect({ record, index })}`;
341
+ dbg = `, dbg: ${JSON.stringify({ record, index })}`;
346
342
  }
347
343
  throw new Error(`Bulk send record is missing the action property${dbg}`);
348
344
  }
@@ -796,19 +792,33 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
796
792
  });
797
793
  }
798
794
 
795
+ function isConnectionErrorMessage(err) {
796
+ const msg = get(err, 'message', '');
797
+ return msg.includes('No Living connections') || msg.includes('ECONNREFUSED');
798
+ }
799
+
800
+ function isErrorRetryable(err) {
801
+ const checkErrorMsg = isRetryableError(err);
802
+
803
+ if (checkErrorMsg) {
804
+ return true;
805
+ }
806
+
807
+ const isRejectedError = get(err, 'body.error.type') === 'es_rejected_execution_exception';
808
+ const isConnectionError = isConnectionErrorMessage(err);
809
+
810
+ if (isRejectedError || isConnectionError) {
811
+ return true;
812
+ }
813
+
814
+ return false;
815
+ }
816
+
799
817
  function _errorHandler(fn, data, reject, fnName = '->unknown()') {
800
818
  const retry = _retryFn(fn, data, reject);
819
+
801
820
  return function _errorHandlerFn(err) {
802
- let retryable = false;
803
- if (isRetryableError(err)) {
804
- retryable = true;
805
- } else {
806
- const isRejectedError = get(err, 'body.error.type') === 'es_rejected_execution_exception';
807
- const isConnectionError = get(err, 'message', '').includes('No Living connections');
808
- if (isRejectedError || isConnectionError) {
809
- retryable = true;
810
- }
811
- }
821
+ const retryable = isErrorRetryable(err);
812
822
 
813
823
  if (retryable) {
814
824
  retry();
@@ -906,8 +916,12 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
906
916
  * please use getClientMetadata
907
917
  * */
908
918
  function getESVersion() {
909
- const newClientVersion = get(client, '__meta.version');
910
- const esVersion = newClientVersion || get(client, 'transport._config.apiVersion', '6.5');
919
+ const newClientVersion = get(client, '__meta.majorVersion');
920
+
921
+ if (newClientVersion) return newClientVersion;
922
+
923
+ // legacy
924
+ const esVersion = get(client, 'transport._config.apiVersion', '6.5');
911
925
 
912
926
  if (esVersion && isString(esVersion)) {
913
927
  const [majorVersion] = esVersion.split('.');
@@ -918,28 +932,30 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
918
932
  }
919
933
 
920
934
  function getClientMetadata() {
921
- const newClientVersion = get(client, '__meta.version');
922
- const esVersion = newClientVersion || get(client, 'transport._config.apiVersion', '6.5');
923
- const distribution = get(client, '__meta.distribution', ElasticsearchDistribution.elasticsearch);
935
+ if (client.__meta) {
936
+ return client.__meta;
937
+ }
938
+
939
+ const esVersion = get(client, 'transport._config.apiVersion', '6.5');
940
+ const distribution = ElasticsearchDistribution.elasticsearch;
941
+ const [majorVersion = 6, minorVersion = 5] = esVersion.split('.').map(toNumber);
924
942
 
925
943
  return {
926
944
  distribution,
927
- version: esVersion
945
+ version: esVersion,
946
+ majorVersion,
947
+ minorVersion
928
948
  };
929
949
  }
930
950
 
931
951
  function isElasticsearch6() {
932
- const { distribution, version: esVersion } = getClientMetadata();
933
- const parsedVersion = toNumber(esVersion.split('.', 1)[0]);
934
-
935
- return distribution === ElasticsearchDistribution.elasticsearch && parsedVersion === 6;
952
+ const { distribution, majorVersion } = getClientMetadata();
953
+ return distribution === ElasticsearchDistribution.elasticsearch && majorVersion === 6;
936
954
  }
937
955
 
938
956
  function isElasticsearch8() {
939
- const { distribution, version: esVersion } = getClientMetadata();
940
- const parsedVersion = toNumber(esVersion.split('.', 1)[0]);
941
-
942
- return distribution === ElasticsearchDistribution.elasticsearch && parsedVersion === 8;
957
+ const { distribution, majorVersion } = getClientMetadata();
958
+ return distribution === ElasticsearchDistribution.elasticsearch && majorVersion === 8;
943
959
  }
944
960
 
945
961
  function _fixMappingRequest(_params, isTemplate) {
@@ -1245,11 +1261,13 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
1245
1261
  validateGeoParameters,
1246
1262
  getClientMetadata,
1247
1263
  isElasticsearch6,
1264
+ isElasticsearch8,
1248
1265
  // The APIs below are deprecated and should be removed.
1249
1266
  index_exists: indexExists,
1250
1267
  index_create: indexCreate,
1251
1268
  index_refresh: indexRefresh,
1252
1269
  index_recovery: indexRecovery,
1253
1270
  getESVersion,
1271
+ isErrorRetryable
1254
1272
  };
1255
1273
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@terascope/elasticsearch-api",
3
3
  "displayName": "Elasticsearch API",
4
- "version": "3.3.1",
4
+ "version": "3.3.3",
5
5
  "description": "Elasticsearch client api used across multiple services, handles retries and exponential backoff",
6
6
  "homepage": "https://github.com/terascope/teraslice/tree/master/packages/elasticsearch-api#readme",
7
7
  "bugs": {
@@ -18,18 +18,20 @@
18
18
  "test:8": "ELASTICSEARCH_VERSION='8.1.2' yarn run test",
19
19
  "test:debug": "ts-scripts test --debug . --",
20
20
  "test:legacy": "LEGACY_CLIENT=true yarn run test",
21
- "test:opensearch": "TEST_OPENSEARCH='true' yarn run test",
21
+ "test:opensearch": "TEST_OPENSEARCH='true' RESTRAINED_OPENSEARCH='true' yarn run test",
22
22
  "test:watch": "ts-scripts test --watch . --"
23
23
  },
24
24
  "dependencies": {
25
+ "@terascope/types": "^0.11.0",
25
26
  "@terascope/utils": "^0.45.1",
26
27
  "bluebird": "^3.7.2",
27
28
  "setimmediate": "^1.0.5"
28
29
  },
29
30
  "devDependencies": {
30
- "@opensearch-project/opensearch": "^1.0.2",
31
+ "@opensearch-project/opensearch": "^1.1.0",
31
32
  "@types/elasticsearch": "^5.0.40",
32
33
  "elasticsearch": "^15.4.1",
34
+ "elasticsearch-store": "^0.65.1",
33
35
  "elasticsearch6": "npm:@elastic/elasticsearch@^6.7.0",
34
36
  "elasticsearch7": "npm:@elastic/elasticsearch@^7.0.0",
35
37
  "elasticsearch8": "npm:@elastic/elasticsearch@^8.0.0"
@@ -1,29 +1,49 @@
1
1
  'use strict';
2
2
 
3
3
  const { debugLogger, chunk, pMap } = require('@terascope/utils');
4
+ const { ElasticsearchTestHelpers } = require('elasticsearch-store');
4
5
  const elasticsearchAPI = require('../index');
5
- const { data } = require('./helpers/data');
6
- const { TEST_INDEX_PREFIX } = require('./helpers/config');
6
+
7
7
  const {
8
- makeClient, cleanupIndex,
9
- waitForData, formatUploadData
10
- } = require('./helpers/elasticsearch');
8
+ makeClient, cleanupIndex, waitForData,
9
+ EvenDateData, TEST_INDEX_PREFIX
10
+ } = ElasticsearchTestHelpers;
11
11
 
12
12
  const THREE_MINUTES = 3 * 60 * 1000;
13
13
 
14
14
  jest.setTimeout(THREE_MINUTES + 30000);
15
15
 
16
+ function formatUploadData(
17
+ index, data, isES8ClientTest = false
18
+ ) {
19
+ const results = [];
20
+
21
+ data.forEach((record) => {
22
+ const meta = { _index: index };
23
+
24
+ if (!isES8ClientTest) {
25
+ meta._type = '_doc';
26
+ }
27
+
28
+ results.push({ action: { index: meta }, data: record });
29
+ });
30
+
31
+ return results;
32
+ }
33
+
16
34
  describe('bulkSend can work with congested queues', () => {
17
35
  const logger = debugLogger('congested_test');
18
36
  const index = `${TEST_INDEX_PREFIX}_congested_queues_`;
19
37
 
20
38
  let client;
21
39
  let api;
40
+ let isElasticsearch8 = false;
22
41
 
23
42
  beforeAll(async () => {
24
43
  client = await makeClient();
25
44
  await cleanupIndex(client, index);
26
45
  api = elasticsearchAPI(client, logger);
46
+ isElasticsearch8 = api.isElasticsearch8();
27
47
  });
28
48
 
29
49
  afterAll(async () => {
@@ -31,13 +51,13 @@ describe('bulkSend can work with congested queues', () => {
31
51
  });
32
52
 
33
53
  it('can get correct data even with congested queues', async () => {
34
- const chunkedData = chunk(data, 50);
54
+ const chunkedData = chunk(EvenDateData.data, 50);
35
55
 
36
56
  await pMap(chunkedData, async (cData) => {
37
- const formattedData = formatUploadData(index, cData);
57
+ const formattedData = formatUploadData(index, cData, isElasticsearch8);
38
58
  return api.bulkSend(formattedData);
39
59
  }, { concurrency: 9 });
40
60
 
41
- await waitForData(client, index, data.length, logger, THREE_MINUTES);
61
+ await waitForData(client, index, EvenDateData.data.length, logger, THREE_MINUTES);
42
62
  });
43
63
  });
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const { ElasticsearchTestHelpers } = require('elasticsearch-store');
4
+ const { debugLogger } = require('@terascope/utils');
5
+ const opensearch = require('@opensearch-project/opensearch');
6
+ const API = require('../index');
7
+
8
+ const logger = debugLogger('retry spec');
9
+ const port = '1111';
10
+
11
+ const { data } = ElasticsearchTestHelpers.EvenDateData;
12
+
13
+ const body = ElasticsearchTestHelpers.formatUploadData('retry-error-test', data);
14
+
15
+ describe('retry behavior', () => {
16
+ it('will work with opensearch when connection cannot be established', async () => {
17
+ expect.assertions(4);
18
+ const client = new opensearch.Client({ node: `http://127.0.0.1:${port}` });
19
+ const { isErrorRetryable } = API(client, logger);
20
+
21
+ try {
22
+ await client.bulk({ body });
23
+ throw Error('should have thrown');
24
+ } catch (err) {
25
+ expect(err).toBeDefined();
26
+ expect(isErrorRetryable(err)).toBeTrue();
27
+ }
28
+
29
+ // we try again as we have seen the second call be marked as unretryable
30
+ try {
31
+ await client.bulk({ body });
32
+ throw Error('should have thrown');
33
+ } catch (err) {
34
+ expect(err).toBeDefined();
35
+ expect(isErrorRetryable(err)).toBeTrue();
36
+ }
37
+ });
38
+ });
package/types/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // Type definitions for elasticsearch-api
2
2
  // Project: @terascope/elasticsearch-api
3
3
 
4
- import * as es from 'elasticsearch';
4
+ import { ClientParams, ClientResponse } from '@terascope/types';
5
5
  import { Logger } from '@terascope/utils';
6
6
  import { ClientMetadata } from '@terascope/types'
7
7
 
@@ -17,16 +17,16 @@ declare namespace elasticsearchAPI {
17
17
  }
18
18
 
19
19
  export interface Client {
20
- search: (query: es.SearchParams) => Promise<es.SearchResponse | any[]>;
21
- count: (query: es.CountParams) => Promise<number>;
22
- get: (query: es.GetParams, fullResponse?: boolean) => Promise<any>;
23
- mget: (query: es.MGetParams) => Promise<any>;
24
- index: (query: es.IndexDocumentParams) => Promise<any>;
25
- indexWithId: (query: es.IndexDocumentParams) => Promise<any>;
20
+ search: (query: ClientParams.SearchParams) => Promise<ClientResponse.SearchResponse | any[]>;
21
+ count: (query: ClientParams.CountParams) => Promise<number>;
22
+ get: (query: ClientParams.GetParams, fullResponse?: boolean) => Promise<any>;
23
+ mget: (query: ClientParams.MGetParams) => Promise<any>;
24
+ index: (query: ClientParams.IndexParams) => Promise<any>;
25
+ indexWithId: (query: ClientParams.IndexParams) => Promise<any>;
26
26
  isAvailable: (index: string, recordType?: string) => Promise<any>;
27
- create: (query: es.CreateDocumentParams) => Promise<any>;
28
- update: (query: es.UpdateDocumentParams) => Promise<any>;
29
- remove: (query: es.DeleteDocumentParams) => Promise<es.DeleteDocumentResponse>;
27
+ create: (query: ClientParams.CreateParams) => Promise<any>;
28
+ update: (query: ClientParams.UpdateParams) => Promise<any>;
29
+ remove: (query: ClientParams.DeleteParams) => Promise<es.DeleteDocumentResponse>;
30
30
  version: () => Promise<boolean>;
31
31
  putTemplate: (template: any, name: string) => Promise<any>;
32
32
  /**
@@ -37,11 +37,11 @@ declare namespace elasticsearchAPI {
37
37
  bulkSend: (data: BulkRecord[]) => Promise<number>;
38
38
  nodeInfo: (query: any) => Promise<any>;
39
39
  nodeStats: (query: any) => Promise<any>;
40
- buildQuery: (opConfig: Config, msg: any) => es.SearchParams;
41
- indexExists: (query: es.ExistsParams) => Promise<boolean>;
42
- indexCreate: (query: es.IndicesCreateParams) => Promise<any>;
43
- indexRefresh: (query: es.IndicesRefreshParams) => Promise<any>;
44
- indexRecovery: (query: es.IndicesRecoveryParams) => Promise<any>;
40
+ buildQuery: (opConfig: Config, msg: any) => ClientParams.SearchParams;
41
+ indexExists: (query: ClientParams.ExistsParams) => Promise<boolean>;
42
+ indexCreate: (query: ClientParams.IndicesCreateParams) => Promise<any>;
43
+ indexRefresh: (query: ClientParams.IndicesRefreshParams) => Promise<any>;
44
+ indexRecovery: (query: ClientParams.IndicesRecoveryParams) => Promise<any>;
45
45
  indexSetup: (clusterName, newIndex, migrantIndexName, mapping, recordType, clientName) => Promise<boolean>;
46
46
  verifyClient: () => boolean;
47
47
  validateGeoParameters: (opConfig: any) => any;
@@ -1,22 +0,0 @@
1
- 'use strict';
2
-
3
- const {
4
- TEST_INDEX_PREFIX = 'teratest_',
5
- ELASTICSEARCH_HOST = 'http://localhost:9200',
6
- ELASTICSEARCH_API_VERSION = '6.5',
7
- ELASTICSEARCH_VERSION = '6.8.6',
8
- OPENSEARCH_USER = 'admin',
9
- OPENSEARCH_PASSWORD = 'admin',
10
- OPENSEARCH_VERSION = '1.3.0',
11
- RESTRAINED_OPENSEARCH_PORT = process.env.RESTRAINED_OPENSEARCH_PORT || '49206',
12
- RESTRAINED_OPENSEARCH_HOST = `http://${OPENSEARCH_USER}:${OPENSEARCH_PASSWORD}@http://localhost:${RESTRAINED_OPENSEARCH_PORT}`,
13
- } = process.env;
14
-
15
- module.exports = {
16
- TEST_INDEX_PREFIX,
17
- ELASTICSEARCH_HOST,
18
- ELASTICSEARCH_API_VERSION,
19
- ELASTICSEARCH_VERSION,
20
- RESTRAINED_OPENSEARCH_HOST,
21
- OPENSEARCH_VERSION
22
- };