@terascope/elasticsearch-api 3.0.2 → 3.2.0

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,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ // polyfill because opensearch has references to an api that won't exist
4
+ // on the client side, should be able to remove in the future
5
+ require('setimmediate');
6
+
3
7
  const Promise = require('bluebird');
4
8
  const {
5
9
  isTest,
@@ -23,9 +27,12 @@ const {
23
27
  getTypeOf,
24
28
  isProd
25
29
  } = require('@terascope/utils');
30
+ const { ElasticsearchDistribution } = require('@terascope/types');
31
+
26
32
  const { inspect } = require('util');
27
33
 
28
34
  const DOCUMENT_EXISTS = 409;
35
+ const TOO_MANY_REQUESTS = 429;
29
36
 
30
37
  // Module to manage persistence in Elasticsearch.
31
38
  // All functions in this module return promises that must be resolved to get the final result.
@@ -73,8 +80,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
73
80
  }
74
81
 
75
82
  function search(query) {
76
- const esVersion = getESVersion();
77
- if (esVersion >= 7) {
83
+ if (!isElasticsearch6()) {
78
84
  if (query._sourceExclude) {
79
85
  query._sourceExcludes = query._sourceExclude.slice();
80
86
  delete query._sourceExclude;
@@ -101,7 +107,10 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
101
107
 
102
108
  function _runRequest() {
103
109
  clientBase[endpoint](query)
104
- .then(resolve)
110
+ .then((rawResponse) => {
111
+ const response = get(rawResponse, 'body', rawResponse);
112
+ resolve(response);
113
+ })
105
114
  .catch(errHandler);
106
115
  }
107
116
 
@@ -228,31 +237,29 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
228
237
  );
229
238
  return Promise.resolve(true);
230
239
  }
231
- return client.cluster.stats({}).then((data) => {
232
- if (!_checkVersion(data.nodes.versions[0])) {
233
- return Promise.resolve();
234
- }
235
- return client.indices
236
- .getSettings({})
237
- .then((results) => {
238
- const resultIndex = _verifyIndex(results, config.index);
239
- if (resultIndex.found) {
240
- resultIndex.indexWindowSize.forEach((ind) => {
241
- logger.warn(
242
- `max_result_window for index: ${ind.name} is set at ${ind.windowSize}. On very large indices it is possible that a slice can not be divided to stay below this limit. If that occurs an error will be thrown by Elasticsearch and the slice can not be processed. Increasing max_result_window in the Elasticsearch index settings will resolve the problem.`
243
- );
244
- });
245
- } else {
246
- const error = new TSError('index specified in reader does not exist', {
247
- statusCode: 404,
248
- });
249
- return Promise.reject(error);
250
- }
251
240
 
252
- return Promise.resolve();
253
- })
254
- .catch((err) => Promise.reject(new TSError(err)));
255
- });
241
+ return client.indices
242
+ .getSettings({})
243
+ .then((results) => {
244
+ const settingsData = results.body && results.meta ? results.body : results;
245
+ const resultIndex = _verifyIndex(settingsData, config.index);
246
+
247
+ if (resultIndex.found) {
248
+ resultIndex.indexWindowSize.forEach((ind) => {
249
+ logger.warn(
250
+ `max_result_window for index: ${ind.name} is set at ${ind.windowSize}. On very large indices it is possible that a slice can not be divided to stay below this limit. If that occurs an error will be thrown by Elasticsearch and the slice can not be processed. Increasing max_result_window in the Elasticsearch index settings will resolve the problem.`
251
+ );
252
+ });
253
+ } else {
254
+ const error = new TSError('index specified in reader does not exist', {
255
+ statusCode: 404,
256
+ });
257
+ return Promise.reject(error);
258
+ }
259
+
260
+ return Promise.resolve();
261
+ })
262
+ .catch((err) => Promise.reject(new TSError(err)));
256
263
  }
257
264
 
258
265
  function putTemplate(template, name) {
@@ -291,7 +298,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
291
298
  continue;
292
299
  }
293
300
 
294
- if (item.error.type === 'es_rejected_execution_exception') {
301
+ if (item.status === TOO_MANY_REQUESTS || item.error.type === 'es_rejected_execution_exception') {
295
302
  if (actionRecords[i] == null) {
296
303
  // this error should not happen in production,
297
304
  // only in tests where the bulk function is mocked
@@ -340,7 +347,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
340
347
  throw new Error(`Bulk send record is missing the action property${dbg}`);
341
348
  }
342
349
 
343
- if (getESVersion() >= 7) {
350
+ if (!isElasticsearch6()) {
344
351
  const actionKey = getFirstKey(record.action);
345
352
  const { _type, ...withoutTypeAction } = record.action[actionKey];
346
353
  // if data is specified return both
@@ -357,10 +364,11 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
357
364
  return record.data ? [record.action, record.data] : [record.action];
358
365
  });
359
366
 
360
- const result = await _clientRequest('bulk', { body });
367
+ const response = await _clientRequest('bulk', { body });
368
+ const results = response.body ? response.body : response;
361
369
 
362
- if (!result.errors) {
363
- return result.items.reduce((c, item) => {
370
+ if (!results.errors) {
371
+ return results.items.reduce((c, item) => {
364
372
  const [value] = Object.values(item);
365
373
  // ignore non-successful status codes
366
374
  if (value.status != null && value.status >= 400) return c;
@@ -370,7 +378,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
370
378
 
371
379
  const {
372
380
  retry, successful, error, reason
373
- } = _filterRetryRecords(actionRecords, result);
381
+ } = _filterRetryRecords(actionRecords, results);
374
382
 
375
383
  if (error) {
376
384
  throw new Error(`bulk send error: ${reason}`);
@@ -631,14 +639,19 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
631
639
  client
632
640
  .search(queryParam)
633
641
  .then((data) => {
634
- const { failures, failed } = data._shards;
642
+ const failuresReasons = [];
643
+ const results = data.body ? data.body : data;
644
+ const { failures, failed } = results._shards;
645
+
635
646
  if (!failed) {
636
- resolve(data);
647
+ resolve(results);
637
648
  return;
638
649
  }
639
650
 
651
+ failuresReasons.push(...failures);
652
+
640
653
  const reasons = uniq(
641
- flatten(failures.map((shard) => shard.reason.type))
654
+ flatten(failuresReasons.map((shard) => shard.reason.type))
642
655
  );
643
656
 
644
657
  if (
@@ -665,13 +678,13 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
665
678
  }
666
679
 
667
680
  function _esV7adjustments(queryParam) {
668
- if (getESVersion() >= 7) {
681
+ if (!isElasticsearch6()) {
669
682
  queryParam.trackTotalHits = true;
670
683
  }
671
684
  }
672
685
 
673
686
  function _adjustTypeForEs7(query) {
674
- if (getESVersion() >= 7) {
687
+ if (!isElasticsearch6()) {
675
688
  if (Array.isArray(query)) {
676
689
  return _removeTypeFromBulkRequest(query);
677
690
  }
@@ -682,7 +695,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
682
695
  }
683
696
 
684
697
  function _removeTypeFromBulkRequest(query) {
685
- if (getESVersion() < 7) return query;
698
+ if (isElasticsearch6()) return query;
686
699
 
687
700
  return query.map((queryItem) => {
688
701
  if (isSimpleObject(queryItem)) {
@@ -812,11 +825,6 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
812
825
  };
813
826
  }
814
827
 
815
- function _checkVersion(str) {
816
- const num = Number(str.replace(/\./g, ''));
817
- return num >= 210;
818
- }
819
-
820
828
  function isAvailable(index, recordType) {
821
829
  const query = {
822
830
  index,
@@ -870,7 +878,7 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
870
878
  });
871
879
  });
872
880
  }
873
-
881
+ // TODO: verifyClient needs to be checked with new client usage
874
882
  /**
875
883
  * Verify the state of the underlying es client.
876
884
  * Will throw error if in fatal state, like the connection is closed.
@@ -894,16 +902,46 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
894
902
 
895
903
  return true;
896
904
  }
897
-
905
+ /** This is deprecated as an external api,
906
+ * please use getClientMetadata
907
+ * */
898
908
  function getESVersion() {
899
- const esVersion = get(client, 'transport._config.apiVersion', '6.5');
909
+ const newClientVersion = get(client, '__meta.version');
910
+ const esVersion = newClientVersion || get(client, 'transport._config.apiVersion', '6.5');
911
+
900
912
  if (esVersion && isString(esVersion)) {
901
913
  const [majorVersion] = esVersion.split('.');
902
914
  return toNumber(majorVersion);
903
915
  }
916
+
904
917
  return 6;
905
918
  }
906
919
 
920
+ 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);
924
+
925
+ return {
926
+ distribution,
927
+ version: esVersion
928
+ };
929
+ }
930
+
931
+ function isElasticsearch6() {
932
+ const { distribution, version: esVersion } = getClientMetadata();
933
+ const parsedVersion = toNumber(esVersion.split('.', 1)[0]);
934
+
935
+ return distribution === ElasticsearchDistribution.elasticsearch && parsedVersion === 6;
936
+ }
937
+
938
+ function isElasticsearch8() {
939
+ const { distribution, version: esVersion } = getClientMetadata();
940
+ const parsedVersion = toNumber(esVersion.split('.', 1)[0]);
941
+
942
+ return distribution === ElasticsearchDistribution.elasticsearch && parsedVersion === 8;
943
+ }
944
+
907
945
  function _fixMappingRequest(_params, isTemplate) {
908
946
  if (!_params || !_params.body) {
909
947
  throw new Error('Invalid mapping request');
@@ -911,17 +949,14 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
911
949
  const params = cloneDeep(_params);
912
950
  const defaultParams = {};
913
951
 
914
- const esVersion = getESVersion();
915
- if (esVersion >= 6) {
916
- if (params.body.template) {
917
- if (isTemplate) {
918
- params.body.index_patterns = castArray(params.body.template).slice();
919
- }
920
- delete params.body.template;
952
+ if (params.body.template != null) {
953
+ if (isTemplate && params.body.index_patterns == null) {
954
+ params.body.index_patterns = castArray(params.body.template).slice();
921
955
  }
956
+ delete params.body.template;
922
957
  }
923
958
 
924
- if (esVersion >= 7) {
959
+ if (!isElasticsearch6()) {
925
960
  const typeMappings = get(params.body, 'mappings', {});
926
961
  if (typeMappings.properties) {
927
962
  defaultParams.includeTypeName = false;
@@ -935,6 +970,11 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
935
970
  });
936
971
  }
937
972
  }
973
+
974
+ if (isElasticsearch8(client)) {
975
+ delete defaultParams.includeTypeName;
976
+ }
977
+
938
978
  return Object.assign({}, defaultParams, params);
939
979
  }
940
980
 
@@ -1028,13 +1068,13 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
1028
1068
  });
1029
1069
  return Promise.reject(error);
1030
1070
  });
1031
- });
1071
+ }).catch((err) => Promise.reject(err));
1032
1072
  }
1033
1073
 
1034
1074
  function _verifyMapping(query, configMapping, recordType) {
1035
1075
  const params = Object.assign({}, query);
1036
- const esVersion = getESVersion();
1037
- if (esVersion > 6) {
1076
+
1077
+ if (!isElasticsearch6()) {
1038
1078
  if (recordType) {
1039
1079
  params.includeTypeName = true;
1040
1080
  }
@@ -1202,12 +1242,14 @@ module.exports = function elasticsearchApi(client, logger, _opConfig) {
1202
1242
  indexRecovery,
1203
1243
  indexSetup,
1204
1244
  verifyClient,
1205
- getESVersion,
1206
1245
  validateGeoParameters,
1246
+ getClientMetadata,
1247
+ isElasticsearch6,
1207
1248
  // The APIs below are deprecated and should be removed.
1208
1249
  index_exists: indexExists,
1209
1250
  index_create: indexCreate,
1210
1251
  index_refresh: indexRefresh,
1211
1252
  index_recovery: indexRecovery,
1253
+ getESVersion,
1212
1254
  };
1213
1255
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@terascope/elasticsearch-api",
3
3
  "displayName": "Elasticsearch API",
4
- "version": "3.0.2",
4
+ "version": "3.2.0",
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": {
@@ -14,15 +14,25 @@
14
14
  "typings": "types/index.d.ts",
15
15
  "scripts": {
16
16
  "test": "ts-scripts test . --",
17
+ "test:7": "ELASTICSEARCH_VERSION='7.9.3' yarn run test",
18
+ "test:8": "ELASTICSEARCH_VERSION='8.1.2' yarn run test",
17
19
  "test:debug": "ts-scripts test --debug . --",
20
+ "test:legacy": "LEGACY_CLIENT=true yarn run test",
21
+ "test:opensearch": "TEST_OPENSEARCH='true' yarn run test",
18
22
  "test:watch": "ts-scripts test --watch . --"
19
23
  },
20
24
  "dependencies": {
21
- "@terascope/utils": "^0.44.1",
22
- "bluebird": "^3.7.2"
25
+ "@terascope/utils": "^0.44.2",
26
+ "bluebird": "^3.7.2",
27
+ "setimmediate": "^1.0.5"
23
28
  },
24
29
  "devDependencies": {
25
- "@types/elasticsearch": "^5.0.40"
30
+ "@opensearch-project/opensearch": "^1.0.2",
31
+ "@types/elasticsearch": "^5.0.40",
32
+ "elasticsearch": "^15.4.1",
33
+ "elasticsearch6": "npm:@elastic/elasticsearch@^6.7.0",
34
+ "elasticsearch7": "npm:@elastic/elasticsearch@^7.0.0",
35
+ "elasticsearch8": "npm:@elastic/elasticsearch@^8.0.0"
26
36
  },
27
37
  "engines": {
28
38
  "node": "^12.22.0 || >=14.17.0",
@@ -33,6 +43,6 @@
33
43
  "registry": "https://registry.npmjs.org/"
34
44
  },
35
45
  "terascope": {
36
- "testSuite": "unit-a"
46
+ "testSuite": "restrained"
37
47
  }
38
48
  }
package/test/api-spec.js CHANGED
@@ -360,7 +360,7 @@ describe('elasticsearch-api', () => {
360
360
  stats: () => Promise.resolve({ _nodes: {} })
361
361
  },
362
362
  cluster: {
363
- stats: () => Promise.resolve({ nodes: { versions: ['5.4.1'] } })
363
+ stats: () => Promise.resolve({ nodes: { versions: ['6.8.1'] } })
364
364
  },
365
365
  __testing: {
366
366
  start: 100,
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const { debugLogger, chunk, pMap } = require('@terascope/utils');
4
+ const elasticsearchAPI = require('../index');
5
+ const { data } = require('./helpers/data');
6
+ const { TEST_INDEX_PREFIX } = require('./helpers/config');
7
+ const {
8
+ makeClient, cleanupIndex,
9
+ waitForData, formatUploadData
10
+ } = require('./helpers/elasticsearch');
11
+
12
+ const THREE_MINUTES = 3 * 60 * 1000;
13
+
14
+ jest.setTimeout(THREE_MINUTES + 30000);
15
+
16
+ describe('bulkSend can work with congested queues', () => {
17
+ const logger = debugLogger('congested_test');
18
+ const index = `${TEST_INDEX_PREFIX}_congested_queues_`;
19
+
20
+ let client;
21
+ let api;
22
+
23
+ beforeAll(async () => {
24
+ client = await makeClient();
25
+ await cleanupIndex(client, index);
26
+ api = elasticsearchAPI(client, logger);
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await cleanupIndex(client, index);
31
+ });
32
+
33
+ it('can get correct data even with congested queues', async () => {
34
+ const chunkedData = chunk(data, 50);
35
+
36
+ await pMap(chunkedData, async (cData) => {
37
+ const formattedData = formatUploadData(index, cData);
38
+ return api.bulkSend(formattedData);
39
+ }, { concurrency: 9 });
40
+
41
+ await waitForData(client, index, data.length, logger, THREE_MINUTES);
42
+ });
43
+ });
@@ -0,0 +1,22 @@
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
+ };