teraslice 2.11.0 → 2.12.1
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/dist/src/interfaces.js +12 -0
- package/dist/src/lib/cluster/cluster_master.js +246 -0
- package/dist/src/lib/cluster/node_master.js +355 -0
- package/dist/src/lib/cluster/services/api.js +663 -0
- package/dist/src/lib/cluster/services/assets.js +226 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/index.js +192 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8s.js +481 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sResource.js +414 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sState.js +59 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetes/utils.js +43 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/index.js +192 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/interfaces.js +2 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8s.js +423 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sDeploymentResource.js +60 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sJobResource.js +55 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sResource.js +359 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sServiceResource.js +37 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sState.js +60 -0
- package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/utils.js +170 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/dispatch.js +13 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/index.js +526 -0
- package/dist/src/lib/cluster/services/cluster/backends/native/messaging.js +547 -0
- package/dist/src/lib/cluster/services/cluster/backends/state-utils.js +26 -0
- package/dist/src/lib/cluster/services/cluster/index.js +17 -0
- package/dist/src/lib/cluster/services/execution.js +435 -0
- package/dist/src/lib/cluster/services/index.js +6 -0
- package/dist/src/lib/cluster/services/interfaces.js +2 -0
- package/dist/src/lib/cluster/services/jobs.js +454 -0
- package/dist/src/lib/config/default-sysconfig.js +26 -0
- package/dist/src/lib/config/index.js +22 -0
- package/dist/src/lib/config/schemas/system.js +360 -0
- package/dist/src/lib/storage/analytics.js +86 -0
- package/dist/src/lib/storage/assets.js +401 -0
- package/dist/src/lib/storage/backends/elasticsearch_store.js +494 -0
- package/dist/src/lib/storage/backends/mappings/analytics.js +50 -0
- package/dist/src/lib/storage/backends/mappings/asset.js +41 -0
- package/dist/src/lib/storage/backends/mappings/ex.js +62 -0
- package/dist/src/lib/storage/backends/mappings/job.js +38 -0
- package/dist/src/lib/storage/backends/mappings/state.js +38 -0
- package/dist/src/lib/storage/backends/s3_store.js +237 -0
- package/dist/src/lib/storage/execution.js +300 -0
- package/dist/src/lib/storage/index.js +7 -0
- package/dist/src/lib/storage/jobs.js +81 -0
- package/dist/src/lib/storage/state.js +255 -0
- package/dist/src/lib/utils/api_utils.js +157 -0
- package/dist/src/lib/utils/asset_utils.js +94 -0
- package/dist/src/lib/utils/date_utils.js +52 -0
- package/dist/src/lib/utils/encoding_utils.js +27 -0
- package/dist/src/lib/utils/events.js +4 -0
- package/dist/src/lib/utils/file_utils.js +124 -0
- package/dist/src/lib/utils/id_utils.js +15 -0
- package/dist/src/lib/utils/port_utils.js +32 -0
- package/dist/src/lib/workers/assets/index.js +3 -0
- package/dist/src/lib/workers/assets/loader-executable.js +40 -0
- package/dist/src/lib/workers/assets/loader.js +73 -0
- package/dist/src/lib/workers/assets/spawn.js +55 -0
- package/dist/src/lib/workers/context/execution-context.js +12 -0
- package/dist/src/lib/workers/context/terafoundation-context.js +8 -0
- package/dist/src/lib/workers/execution-controller/execution-analytics.js +188 -0
- package/dist/src/lib/workers/execution-controller/index.js +1024 -0
- package/dist/src/lib/workers/execution-controller/recovery.js +151 -0
- package/dist/src/lib/workers/execution-controller/scheduler.js +390 -0
- package/dist/src/lib/workers/execution-controller/slice-analytics.js +96 -0
- package/dist/src/lib/workers/helpers/job.js +80 -0
- package/dist/src/lib/workers/helpers/op-analytics.js +22 -0
- package/dist/src/lib/workers/helpers/terafoundation.js +34 -0
- package/dist/src/lib/workers/helpers/worker-shutdown.js +169 -0
- package/dist/src/lib/workers/metrics/index.js +108 -0
- package/dist/src/lib/workers/worker/index.js +378 -0
- package/dist/src/lib/workers/worker/slice.js +122 -0
- package/dist/test/config/schemas/system_schema-spec.js +37 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8s-spec.js +316 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sResource-spec.js +795 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-multicluster-spec.js +67 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-spec.js +84 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/utils-spec.js +132 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8s-v2-spec.js +455 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sResource-v2-spec.js +818 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-multicluster-v2-spec.js +67 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-v2-spec.js +84 -0
- package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/utils-v2-spec.js +320 -0
- package/dist/test/lib/cluster/services/cluster/backends/state-utils-spec.js +37 -0
- package/dist/test/node_master-spec.js +188 -0
- package/dist/test/services/api-spec.js +80 -0
- package/dist/test/services/assets-spec.js +158 -0
- package/dist/test/services/messaging-spec.js +440 -0
- package/dist/test/storage/assets_storage-spec.js +95 -0
- package/dist/test/storage/s3_store-spec.js +138 -0
- package/dist/test/test.config.js +8 -0
- package/dist/test/test.setup.js +6 -0
- package/dist/test/utils/api_utils-spec.js +86 -0
- package/dist/test/utils/asset_utils-spec.js +141 -0
- package/dist/test/utils/elastic_utils-spec.js +25 -0
- package/dist/test/workers/execution-controller/execution-controller-spec.js +371 -0
- package/dist/test/workers/execution-controller/execution-special-test-cases-spec.js +520 -0
- package/dist/test/workers/execution-controller/execution-test-cases-spec.js +338 -0
- package/dist/test/workers/execution-controller/recovery-spec.js +160 -0
- package/dist/test/workers/execution-controller/scheduler-spec.js +249 -0
- package/dist/test/workers/execution-controller/slice-analytics-spec.js +121 -0
- package/dist/test/workers/fixtures/ops/example-op/processor.js +20 -0
- package/dist/test/workers/fixtures/ops/example-op/schema.js +19 -0
- package/dist/test/workers/fixtures/ops/example-reader/fetcher.js +20 -0
- package/dist/test/workers/fixtures/ops/example-reader/schema.js +41 -0
- package/dist/test/workers/fixtures/ops/example-reader/slicer.js +37 -0
- package/dist/test/workers/fixtures/ops/new-op/processor.js +29 -0
- package/dist/test/workers/fixtures/ops/new-op/schema.js +18 -0
- package/dist/test/workers/fixtures/ops/new-reader/fetcher.js +19 -0
- package/dist/test/workers/fixtures/ops/new-reader/schema.js +23 -0
- package/dist/test/workers/fixtures/ops/new-reader/slicer.js +13 -0
- package/dist/test/workers/helpers/configs.js +130 -0
- package/dist/test/workers/helpers/execution-controller-helper.js +49 -0
- package/dist/test/workers/helpers/index.js +5 -0
- package/dist/test/workers/helpers/test-context.js +210 -0
- package/dist/test/workers/helpers/zip-directory.js +25 -0
- package/dist/test/workers/worker/slice-spec.js +333 -0
- package/dist/test/workers/worker/worker-spec.js +356 -0
- package/package.json +94 -94
- package/service.js +0 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import ms from 'ms';
|
|
2
|
+
import { TSError, parseError, isTest, pDelay, pRetry, logError, pWhile, isString, getTypeOf, get, random, isInteger } from '@terascope/utils';
|
|
3
|
+
import elasticsearchApi from '@terascope/elasticsearch-api';
|
|
4
|
+
import { getClient } from '@terascope/job-components';
|
|
5
|
+
import { makeLogger } from '../../workers/helpers/terafoundation.js';
|
|
6
|
+
import { timeseriesIndex } from '../../utils/date_utils.js';
|
|
7
|
+
import analyticsSchema from './mappings/analytics.js';
|
|
8
|
+
import assetSchema from './mappings/asset.js';
|
|
9
|
+
import executionSchema from './mappings/ex.js';
|
|
10
|
+
import jobsSchema from './mappings/job.js';
|
|
11
|
+
import stateSchema from './mappings/state.js';
|
|
12
|
+
function validateId(recordId, recordType) {
|
|
13
|
+
if (!recordId || !isString(recordId)) {
|
|
14
|
+
throw new TSError(`Invalid ${recordType} id given ${getTypeOf(recordId)}`, {
|
|
15
|
+
statusCode: 422
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function validateIdAndRecord(recordId, record, recordType, idField) {
|
|
20
|
+
validateId(recordId, recordType);
|
|
21
|
+
const id = record[idField];
|
|
22
|
+
if (id && id !== recordId) {
|
|
23
|
+
throw new TSError(`${recordType}.${idField} doesn't match request id`, {
|
|
24
|
+
statusCode: 406
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// TODO: fix types here
|
|
29
|
+
function _getTimeout(timeout) {
|
|
30
|
+
if (isInteger(timeout)) {
|
|
31
|
+
// don't allow a timeout of less than 1 second
|
|
32
|
+
if (timeout <= 1000)
|
|
33
|
+
return undefined;
|
|
34
|
+
return ms(timeout);
|
|
35
|
+
}
|
|
36
|
+
if (isString(timeout)) {
|
|
37
|
+
return timeout;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
// TODO: type this better
|
|
42
|
+
export class TerasliceElasticsearchStorage {
|
|
43
|
+
context;
|
|
44
|
+
logger;
|
|
45
|
+
recordType;
|
|
46
|
+
// TODO: should this be here?
|
|
47
|
+
defaultIndexName;
|
|
48
|
+
storageName;
|
|
49
|
+
flushInterval;
|
|
50
|
+
isShuttingDown = false;
|
|
51
|
+
// flag to know if already in a bulk call
|
|
52
|
+
savingBulk = false;
|
|
53
|
+
// Buffer to build up bulk requests. TODO: fix type
|
|
54
|
+
bulkQueue = [];
|
|
55
|
+
idField;
|
|
56
|
+
options;
|
|
57
|
+
mapping;
|
|
58
|
+
api;
|
|
59
|
+
constructor(backendConfig) {
|
|
60
|
+
const { context, indexName, recordType, idField, storageName, bulkSize = 1000, fullResponse = false, logRecord = true, forceRefresh = true, logger } = backendConfig;
|
|
61
|
+
this.context = context;
|
|
62
|
+
this.storageName = storageName;
|
|
63
|
+
this.logger = logger ?? makeLogger(context, 'elasticsearch_backend', { storageName });
|
|
64
|
+
this.recordType = recordType;
|
|
65
|
+
if (recordType === 'analytics') {
|
|
66
|
+
this.mapping = analyticsSchema;
|
|
67
|
+
}
|
|
68
|
+
else if (recordType === 'asset') {
|
|
69
|
+
this.mapping = assetSchema;
|
|
70
|
+
}
|
|
71
|
+
else if (recordType === 'ex') {
|
|
72
|
+
this.mapping = executionSchema;
|
|
73
|
+
}
|
|
74
|
+
else if (recordType === 'job') {
|
|
75
|
+
this.mapping = jobsSchema;
|
|
76
|
+
}
|
|
77
|
+
else if (recordType === 'state') {
|
|
78
|
+
this.mapping = stateSchema;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
throw new Error(`Could not find mapping for recordType: ${recordType}`);
|
|
82
|
+
}
|
|
83
|
+
const config = this.context.sysconfig.teraslice;
|
|
84
|
+
const indexSettings = get(config, ['index_settings', this.storageName], {
|
|
85
|
+
number_of_shards: 5,
|
|
86
|
+
number_of_replicas: 1,
|
|
87
|
+
});
|
|
88
|
+
this.mapping.settings = {
|
|
89
|
+
'index.number_of_shards': indexSettings.number_of_shards,
|
|
90
|
+
'index.number_of_replicas': indexSettings.number_of_replicas,
|
|
91
|
+
};
|
|
92
|
+
this.defaultIndexName = indexName;
|
|
93
|
+
this.idField = idField;
|
|
94
|
+
this.options = {
|
|
95
|
+
bulkSize,
|
|
96
|
+
fullResponse,
|
|
97
|
+
logRecord,
|
|
98
|
+
forceRefresh
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async initialize() {
|
|
102
|
+
const config = this.context.sysconfig.teraslice;
|
|
103
|
+
const isMultiIndex = this.defaultIndexName[this.defaultIndexName.length - 1] === '*';
|
|
104
|
+
let newIndex = this.defaultIndexName;
|
|
105
|
+
if (isMultiIndex) {
|
|
106
|
+
// @ts-expect-error TODO: fix this
|
|
107
|
+
const storeType = this.defaultIndexName.match(/__(.*)\*/)[1];
|
|
108
|
+
const timeseriesFormat = config.index_rollover_frequency[storeType];
|
|
109
|
+
const nameSize = this.defaultIndexName.length - 1;
|
|
110
|
+
newIndex = timeseriesIndex(timeseriesFormat, this.defaultIndexName.slice(0, nameSize)).index;
|
|
111
|
+
}
|
|
112
|
+
const clientName = JSON.stringify({
|
|
113
|
+
connection: config.state.connection,
|
|
114
|
+
index: this.defaultIndexName,
|
|
115
|
+
});
|
|
116
|
+
const connectionConfig = Object.assign({}, config.state);
|
|
117
|
+
if (connectionConfig.connection_cache == null) {
|
|
118
|
+
connectionConfig.connection_cache = true;
|
|
119
|
+
}
|
|
120
|
+
const { connection } = config.state;
|
|
121
|
+
const options = {
|
|
122
|
+
full_response: !!this.options.fullResponse,
|
|
123
|
+
connection,
|
|
124
|
+
};
|
|
125
|
+
await pWhile(async () => {
|
|
126
|
+
try {
|
|
127
|
+
const client = await getClient(this.context, connectionConfig, 'elasticsearch-next');
|
|
128
|
+
this.api = elasticsearchApi(client, this.logger, options);
|
|
129
|
+
await this._createIndex(newIndex);
|
|
130
|
+
await this.api.isAvailable(newIndex, this.recordType);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
const error = new TSError(err, {
|
|
135
|
+
reason: `Failure initializing ${this.recordType} index: ${this.defaultIndexName}`,
|
|
136
|
+
});
|
|
137
|
+
if (error.statusCode >= 400 && error.statusCode < 500) {
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
logError(this.logger, error, `Failed attempt connecting to elasticsearch: ${clientName} (will retry)`);
|
|
141
|
+
await pDelay(isTest ? 0 : random(2000, 4000));
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
// Periodically flush the bulkQueue so we don't end up with cached data lingering.
|
|
146
|
+
this.flushInterval = setInterval(() => {
|
|
147
|
+
// TODO: this might be undefined
|
|
148
|
+
this._flush().catch((err) => {
|
|
149
|
+
logError(this.logger, err, 'background flush failure');
|
|
150
|
+
return null;
|
|
151
|
+
});
|
|
152
|
+
// stager the interval to avoid collisions
|
|
153
|
+
}, random(9000, 11000));
|
|
154
|
+
}
|
|
155
|
+
async get(recordId, index = this.defaultIndexName, fields) {
|
|
156
|
+
validateId(recordId, this.recordType);
|
|
157
|
+
this.logger.trace(`getting record id: ${recordId}`);
|
|
158
|
+
const query = {
|
|
159
|
+
index,
|
|
160
|
+
type: this.recordType,
|
|
161
|
+
id: recordId,
|
|
162
|
+
};
|
|
163
|
+
if (fields) {
|
|
164
|
+
if (!this.api.isElasticsearch6()) {
|
|
165
|
+
query._source_includes = fields;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// @ts-expect-error backwards compatible
|
|
169
|
+
query._sourceInclude = fields;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return this.api.get(query);
|
|
173
|
+
}
|
|
174
|
+
async search(query, from = 0, size = 10000, sort, fields, index = this.defaultIndexName) {
|
|
175
|
+
if (from != null && !isInteger(from)) {
|
|
176
|
+
throw new Error(`from parameter must be a integer, got ${from}`);
|
|
177
|
+
}
|
|
178
|
+
if (size != null && !isInteger(size)) {
|
|
179
|
+
throw new Error(`size parameter must be a integer, got ${size}`);
|
|
180
|
+
}
|
|
181
|
+
if (sort != null && !isString(sort)) {
|
|
182
|
+
throw new Error(`sort parameter must be a string, got ${sort}`);
|
|
183
|
+
}
|
|
184
|
+
const esQuery = {
|
|
185
|
+
index,
|
|
186
|
+
from,
|
|
187
|
+
size,
|
|
188
|
+
type: this.recordType,
|
|
189
|
+
sort,
|
|
190
|
+
};
|
|
191
|
+
if (typeof query === 'string') {
|
|
192
|
+
esQuery.q = query;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
esQuery.body = query;
|
|
196
|
+
}
|
|
197
|
+
if (fields) {
|
|
198
|
+
if (!this.api.isElasticsearch6()) {
|
|
199
|
+
esQuery._source_includes = fields;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// @ts-expect-error backwards compatible
|
|
203
|
+
esQuery._sourceInclude = fields;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return this.api.search(esQuery);
|
|
207
|
+
}
|
|
208
|
+
/*
|
|
209
|
+
* index saves a record to elasticsearch allowing automatic
|
|
210
|
+
* ID creation
|
|
211
|
+
*/
|
|
212
|
+
async index(record, indexArg = this.defaultIndexName) {
|
|
213
|
+
this.logger.trace('indexing record', this.options.logRecord ? record : undefined);
|
|
214
|
+
const query = {
|
|
215
|
+
index: indexArg,
|
|
216
|
+
type: this.recordType,
|
|
217
|
+
body: record,
|
|
218
|
+
refresh: this.options.forceRefresh,
|
|
219
|
+
};
|
|
220
|
+
return this.api.index(query);
|
|
221
|
+
}
|
|
222
|
+
/*
|
|
223
|
+
* index saves a record to elasticsearch with a specified ID.
|
|
224
|
+
* If the document is already there it will be replaced.
|
|
225
|
+
*/
|
|
226
|
+
async indexWithId(recordId, record, index = this.defaultIndexName, timeout) {
|
|
227
|
+
validateIdAndRecord(recordId, record, this.recordType, this.idField);
|
|
228
|
+
this.logger.trace(`indexWithId call with id: ${recordId}, record`, this.options.logRecord ? record : null);
|
|
229
|
+
const query = {
|
|
230
|
+
index,
|
|
231
|
+
type: this.recordType,
|
|
232
|
+
id: recordId,
|
|
233
|
+
body: record,
|
|
234
|
+
refresh: this.options.forceRefresh,
|
|
235
|
+
timeout: _getTimeout(timeout)
|
|
236
|
+
};
|
|
237
|
+
return this.api.indexWithId(query);
|
|
238
|
+
}
|
|
239
|
+
/*
|
|
240
|
+
* Create saves a record to elasticsearch under the provided id.
|
|
241
|
+
* If the record already exists it will not be inserted.
|
|
242
|
+
*/
|
|
243
|
+
async create(record, index = this.defaultIndexName) {
|
|
244
|
+
this.logger.trace('creating record', this.options.logRecord ? record : null);
|
|
245
|
+
const query = {
|
|
246
|
+
index,
|
|
247
|
+
type: this.recordType,
|
|
248
|
+
id: record[this.idField],
|
|
249
|
+
body: record,
|
|
250
|
+
refresh: this.options.forceRefresh,
|
|
251
|
+
};
|
|
252
|
+
return this.api.create(query);
|
|
253
|
+
}
|
|
254
|
+
async count(query, from, sort, index = this.defaultIndexName) {
|
|
255
|
+
if (from != null && !isInteger(from)) {
|
|
256
|
+
throw new Error(`from parameter must be a integer, got ${from}`);
|
|
257
|
+
}
|
|
258
|
+
if (sort != null && !isString(sort)) {
|
|
259
|
+
throw new Error(`sort parameter must be a string, got ${sort}`);
|
|
260
|
+
}
|
|
261
|
+
// TODO: check from
|
|
262
|
+
const esQuery = {
|
|
263
|
+
index,
|
|
264
|
+
type: this.recordType,
|
|
265
|
+
// @ts-expect-error
|
|
266
|
+
from,
|
|
267
|
+
sort,
|
|
268
|
+
};
|
|
269
|
+
if (isString(query)) {
|
|
270
|
+
esQuery.q = query;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
esQuery.body = query;
|
|
274
|
+
}
|
|
275
|
+
return this.api.count(esQuery);
|
|
276
|
+
}
|
|
277
|
+
async update(recordId, updateSpec, index = this.defaultIndexName) {
|
|
278
|
+
validateIdAndRecord(recordId, updateSpec, this.recordType, this.idField);
|
|
279
|
+
this.logger.trace(`updating record ${recordId}, `, this.options.logRecord ? updateSpec : null);
|
|
280
|
+
const query = {
|
|
281
|
+
index,
|
|
282
|
+
type: this.recordType,
|
|
283
|
+
id: recordId,
|
|
284
|
+
body: {
|
|
285
|
+
doc: updateSpec,
|
|
286
|
+
},
|
|
287
|
+
refresh: this.options.forceRefresh,
|
|
288
|
+
// TODO: check this retry
|
|
289
|
+
retry_on_conflict: 3,
|
|
290
|
+
};
|
|
291
|
+
return this.api.update(query);
|
|
292
|
+
}
|
|
293
|
+
async updatePartial(recordId, applyChanges, index = this.defaultIndexName) {
|
|
294
|
+
if (typeof applyChanges !== 'function') {
|
|
295
|
+
throw new Error('Update Partial expected a applyChanges function');
|
|
296
|
+
}
|
|
297
|
+
validateId(recordId, this.recordType);
|
|
298
|
+
const getParams = {
|
|
299
|
+
index,
|
|
300
|
+
type: this.recordType,
|
|
301
|
+
id: recordId,
|
|
302
|
+
};
|
|
303
|
+
const existing = await pRetry(() => this.api.get(getParams, true), {
|
|
304
|
+
matches: ['no_shard_available_action_exception'],
|
|
305
|
+
delay: 1000,
|
|
306
|
+
retries: 10,
|
|
307
|
+
backoff: 5
|
|
308
|
+
});
|
|
309
|
+
const doc = await applyChanges(Object.assign({}, existing._source));
|
|
310
|
+
this.logger.trace(`updating partial record ${recordId}, `, this.options.logRecord ? doc : null);
|
|
311
|
+
validateIdAndRecord(recordId, doc, this.recordType, this.idField);
|
|
312
|
+
const query = {
|
|
313
|
+
index,
|
|
314
|
+
type: this.recordType,
|
|
315
|
+
id: recordId,
|
|
316
|
+
body: doc,
|
|
317
|
+
refresh: this.options.forceRefresh,
|
|
318
|
+
};
|
|
319
|
+
if (!this.api.isElasticsearch6()) {
|
|
320
|
+
query.if_seq_no = existing._seq_no;
|
|
321
|
+
query.if_primary_term = existing._primary_term;
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
query.version = existing._version;
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
await this.api.indexWithId(query);
|
|
328
|
+
return doc;
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
// if there is a version conflict
|
|
332
|
+
if (err.statusCode === 409 && err.message.includes('version conflict')) {
|
|
333
|
+
this.logger.debug({ error: err }, `version conflict when updating "${recordId}" (${this.recordType})`);
|
|
334
|
+
return this.updatePartial(recordId, applyChanges, index);
|
|
335
|
+
}
|
|
336
|
+
throw new TSError(err);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async remove(recordId, index = this.defaultIndexName) {
|
|
340
|
+
validateId(recordId, this.recordType);
|
|
341
|
+
this.logger.trace(`removing record ${recordId}`);
|
|
342
|
+
const query = {
|
|
343
|
+
index,
|
|
344
|
+
type: this.recordType,
|
|
345
|
+
id: recordId,
|
|
346
|
+
refresh: this.options.forceRefresh,
|
|
347
|
+
};
|
|
348
|
+
return this.api.remove(query);
|
|
349
|
+
}
|
|
350
|
+
async bulk(record, type = 'index', index = this.defaultIndexName) {
|
|
351
|
+
if (this.isShuttingDown) {
|
|
352
|
+
throw new TSError('Unable to send bulk record after shutdown', {
|
|
353
|
+
context: {
|
|
354
|
+
recordType: this.recordType,
|
|
355
|
+
record,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const action = {
|
|
360
|
+
[type]: {
|
|
361
|
+
_index: index,
|
|
362
|
+
_type: this.recordType,
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
this.bulkQueue.push({
|
|
366
|
+
action,
|
|
367
|
+
data: type === 'delete' ? undefined : record
|
|
368
|
+
});
|
|
369
|
+
// We only flush once enough records have accumulated for it to make sense.
|
|
370
|
+
if (this.bulkQueue.length >= this.options.bulkSize) {
|
|
371
|
+
this.logger.trace(`flushing bulk queue ${this.bulkQueue.length}`);
|
|
372
|
+
return this._flush();
|
|
373
|
+
}
|
|
374
|
+
// Bulk saving is a background operation so we don't have
|
|
375
|
+
// anything meaningful to return.
|
|
376
|
+
return Promise.resolve(true);
|
|
377
|
+
}
|
|
378
|
+
_destroy(err) {
|
|
379
|
+
this.bulkQueue = [];
|
|
380
|
+
this.isShuttingDown = true;
|
|
381
|
+
if (err) {
|
|
382
|
+
throw err;
|
|
383
|
+
}
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
async shutdown(forceShutdown = false) {
|
|
387
|
+
const startTime = Date.now();
|
|
388
|
+
this.logger.trace(`shutdown store, took ${ms(Date.now() - startTime)}`);
|
|
389
|
+
clearInterval(this.flushInterval);
|
|
390
|
+
if (forceShutdown !== true) {
|
|
391
|
+
return this._flush(true);
|
|
392
|
+
}
|
|
393
|
+
const destroy = this._destroy.bind(this);
|
|
394
|
+
const timeout = this.context.sysconfig.teraslice.shutdown_timeout;
|
|
395
|
+
this.logger.trace(`attempting to shutdown, will destroy in ${timeout}`);
|
|
396
|
+
setTimeout(destroy, timeout).unref();
|
|
397
|
+
try {
|
|
398
|
+
await this._flush(true);
|
|
399
|
+
this._destroy();
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
this._destroy(err);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async bulkSend(bulkRequest) {
|
|
406
|
+
return this.api.bulkSend(bulkRequest);
|
|
407
|
+
}
|
|
408
|
+
async _flush(shuttingDown = false) {
|
|
409
|
+
if (!this.bulkQueue.length)
|
|
410
|
+
return;
|
|
411
|
+
if (!shuttingDown && this.savingBulk)
|
|
412
|
+
return;
|
|
413
|
+
this.savingBulk = true;
|
|
414
|
+
const bulkRequest = this.bulkQueue.slice();
|
|
415
|
+
this.bulkQueue = [];
|
|
416
|
+
try {
|
|
417
|
+
const recordCount = await this.bulkSend(bulkRequest);
|
|
418
|
+
const extraMsg = shuttingDown ? ', on shutdown' : '';
|
|
419
|
+
this.logger.debug(`flushed ${recordCount}${extraMsg} records to index ${this.defaultIndexName}`);
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
this.savingBulk = false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
async sendTemplate(mapping) {
|
|
426
|
+
if (mapping.template) {
|
|
427
|
+
const clusterName = this.context.sysconfig.teraslice.name;
|
|
428
|
+
const name = `${clusterName}_${this.recordType}_template`;
|
|
429
|
+
// setting template name to reflect current teraslice instance name to help prevent
|
|
430
|
+
// conflicts with differing versions of teraslice with same elastic db
|
|
431
|
+
if (mapping.template) {
|
|
432
|
+
if (!mapping.template.match(clusterName)) {
|
|
433
|
+
mapping.template = `${clusterName}${mapping.template}`;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return this.putTemplate(mapping, name);
|
|
437
|
+
}
|
|
438
|
+
return Promise.resolve(true);
|
|
439
|
+
}
|
|
440
|
+
async _createIndex(index = this.defaultIndexName) {
|
|
441
|
+
// @ts-expect-error TODO: check type missing id
|
|
442
|
+
const existQuery = { index };
|
|
443
|
+
const exists = await this.api.indexExists(existQuery);
|
|
444
|
+
if (!exists) {
|
|
445
|
+
// Make sure the index exists before we do anything else.
|
|
446
|
+
const createQuery = {
|
|
447
|
+
index,
|
|
448
|
+
body: this.mapping,
|
|
449
|
+
};
|
|
450
|
+
try {
|
|
451
|
+
await this.sendTemplate(this.mapping);
|
|
452
|
+
return await this.api.indexCreate(createQuery);
|
|
453
|
+
}
|
|
454
|
+
catch (err) {
|
|
455
|
+
// It's not really an error if it's just that the index is already there
|
|
456
|
+
if (parseError(err).includes('already_exists_exception')) {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
const error = new TSError(err, {
|
|
460
|
+
reason: `Could not create index: ${index}`,
|
|
461
|
+
});
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Index already exists. nothing to do.
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
async refresh(index = this.defaultIndexName) {
|
|
469
|
+
const query = { index };
|
|
470
|
+
return this.api.indexRefresh(query);
|
|
471
|
+
}
|
|
472
|
+
// TODO: fix type here
|
|
473
|
+
async putTemplate(template, name) {
|
|
474
|
+
return this.api.putTemplate(template, name);
|
|
475
|
+
}
|
|
476
|
+
verifyClient() {
|
|
477
|
+
if (this.isShuttingDown)
|
|
478
|
+
return false;
|
|
479
|
+
return this.api.verifyClient();
|
|
480
|
+
}
|
|
481
|
+
async waitForClient() {
|
|
482
|
+
if (this.api.verifyClient())
|
|
483
|
+
return;
|
|
484
|
+
await pWhile(async () => {
|
|
485
|
+
if (this.isShuttingDown)
|
|
486
|
+
throw new Error('Elasticsearch store is shutdown');
|
|
487
|
+
if (this.api.verifyClient())
|
|
488
|
+
return true;
|
|
489
|
+
await pDelay(100);
|
|
490
|
+
return false;
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
//# sourceMappingURL=elasticsearch_store.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
template: '__analytics*',
|
|
3
|
+
mappings: {
|
|
4
|
+
analytics: {
|
|
5
|
+
_all: {
|
|
6
|
+
enabled: false
|
|
7
|
+
},
|
|
8
|
+
dynamic: false,
|
|
9
|
+
properties: {
|
|
10
|
+
ex_id: {
|
|
11
|
+
type: 'keyword'
|
|
12
|
+
},
|
|
13
|
+
job_id: {
|
|
14
|
+
type: 'keyword'
|
|
15
|
+
},
|
|
16
|
+
worker_id: {
|
|
17
|
+
type: 'keyword'
|
|
18
|
+
},
|
|
19
|
+
slice_id: {
|
|
20
|
+
type: 'keyword'
|
|
21
|
+
},
|
|
22
|
+
slicer_id: {
|
|
23
|
+
type: 'keyword'
|
|
24
|
+
},
|
|
25
|
+
op: {
|
|
26
|
+
type: 'keyword'
|
|
27
|
+
},
|
|
28
|
+
order: {
|
|
29
|
+
type: 'integer'
|
|
30
|
+
},
|
|
31
|
+
count: {
|
|
32
|
+
type: 'integer'
|
|
33
|
+
},
|
|
34
|
+
state: {
|
|
35
|
+
type: 'keyword'
|
|
36
|
+
},
|
|
37
|
+
time: {
|
|
38
|
+
type: 'integer'
|
|
39
|
+
},
|
|
40
|
+
memory: {
|
|
41
|
+
type: 'long'
|
|
42
|
+
},
|
|
43
|
+
'@timestamp': {
|
|
44
|
+
type: 'date'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
mappings: {
|
|
3
|
+
asset: {
|
|
4
|
+
_all: {
|
|
5
|
+
enabled: false
|
|
6
|
+
},
|
|
7
|
+
dynamic: false,
|
|
8
|
+
properties: {
|
|
9
|
+
blob: {
|
|
10
|
+
type: 'binary',
|
|
11
|
+
doc_values: false
|
|
12
|
+
},
|
|
13
|
+
name: {
|
|
14
|
+
type: 'keyword'
|
|
15
|
+
},
|
|
16
|
+
version: {
|
|
17
|
+
type: 'keyword'
|
|
18
|
+
},
|
|
19
|
+
id: {
|
|
20
|
+
type: 'keyword'
|
|
21
|
+
},
|
|
22
|
+
description: {
|
|
23
|
+
type: 'keyword'
|
|
24
|
+
},
|
|
25
|
+
arch: {
|
|
26
|
+
type: 'keyword'
|
|
27
|
+
},
|
|
28
|
+
platform: {
|
|
29
|
+
type: 'keyword'
|
|
30
|
+
},
|
|
31
|
+
node_version: {
|
|
32
|
+
type: 'integer'
|
|
33
|
+
},
|
|
34
|
+
_created: {
|
|
35
|
+
type: 'date'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=asset.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
mappings: {
|
|
3
|
+
ex: {
|
|
4
|
+
_all: {
|
|
5
|
+
enabled: false
|
|
6
|
+
},
|
|
7
|
+
dynamic: false,
|
|
8
|
+
properties: {
|
|
9
|
+
active: {
|
|
10
|
+
type: 'boolean'
|
|
11
|
+
},
|
|
12
|
+
job_id: {
|
|
13
|
+
type: 'keyword'
|
|
14
|
+
},
|
|
15
|
+
ex_id: {
|
|
16
|
+
type: 'keyword'
|
|
17
|
+
},
|
|
18
|
+
_context: {
|
|
19
|
+
type: 'keyword'
|
|
20
|
+
},
|
|
21
|
+
_status: {
|
|
22
|
+
type: 'keyword'
|
|
23
|
+
},
|
|
24
|
+
_has_errors: {
|
|
25
|
+
type: 'keyword'
|
|
26
|
+
},
|
|
27
|
+
slicer_hostname: {
|
|
28
|
+
type: 'keyword'
|
|
29
|
+
},
|
|
30
|
+
slicer_port: {
|
|
31
|
+
type: 'keyword'
|
|
32
|
+
},
|
|
33
|
+
recovered_execution: {
|
|
34
|
+
type: 'keyword'
|
|
35
|
+
},
|
|
36
|
+
recovered_slice_type: {
|
|
37
|
+
type: 'keyword'
|
|
38
|
+
},
|
|
39
|
+
metadata: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
enabled: false
|
|
42
|
+
},
|
|
43
|
+
_slicer_stats: {
|
|
44
|
+
type: 'object'
|
|
45
|
+
},
|
|
46
|
+
_created: {
|
|
47
|
+
type: 'date'
|
|
48
|
+
},
|
|
49
|
+
_updated: {
|
|
50
|
+
type: 'date'
|
|
51
|
+
},
|
|
52
|
+
_deleted: {
|
|
53
|
+
type: 'boolean'
|
|
54
|
+
},
|
|
55
|
+
_deleted_on: {
|
|
56
|
+
type: 'date'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=ex.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
settings: {
|
|
3
|
+
'index.number_of_shards': 5,
|
|
4
|
+
'index.number_of_replicas': 1
|
|
5
|
+
},
|
|
6
|
+
mappings: {
|
|
7
|
+
job: {
|
|
8
|
+
_all: {
|
|
9
|
+
enabled: false
|
|
10
|
+
},
|
|
11
|
+
dynamic: false,
|
|
12
|
+
properties: {
|
|
13
|
+
active: {
|
|
14
|
+
type: 'boolean'
|
|
15
|
+
},
|
|
16
|
+
job_id: {
|
|
17
|
+
type: 'keyword'
|
|
18
|
+
},
|
|
19
|
+
_context: {
|
|
20
|
+
type: 'keyword'
|
|
21
|
+
},
|
|
22
|
+
_created: {
|
|
23
|
+
type: 'date'
|
|
24
|
+
},
|
|
25
|
+
_updated: {
|
|
26
|
+
type: 'date'
|
|
27
|
+
},
|
|
28
|
+
_deleted: {
|
|
29
|
+
type: 'boolean'
|
|
30
|
+
},
|
|
31
|
+
_deleted_on: {
|
|
32
|
+
type: 'date'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=job.js.map
|