teraslice 2.11.0 → 2.12.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.
Files changed (118) hide show
  1. package/dist/src/interfaces.js +12 -0
  2. package/dist/src/lib/cluster/cluster_master.js +246 -0
  3. package/dist/src/lib/cluster/node_master.js +355 -0
  4. package/dist/src/lib/cluster/services/api.js +663 -0
  5. package/dist/src/lib/cluster/services/assets.js +226 -0
  6. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/index.js +192 -0
  7. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8s.js +481 -0
  8. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sResource.js +414 -0
  9. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/k8sState.js +59 -0
  10. package/dist/src/lib/cluster/services/cluster/backends/kubernetes/utils.js +43 -0
  11. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/index.js +192 -0
  12. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/interfaces.js +2 -0
  13. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8s.js +423 -0
  14. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sDeploymentResource.js +60 -0
  15. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sJobResource.js +55 -0
  16. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sResource.js +359 -0
  17. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sServiceResource.js +37 -0
  18. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/k8sState.js +60 -0
  19. package/dist/src/lib/cluster/services/cluster/backends/kubernetesV2/utils.js +170 -0
  20. package/dist/src/lib/cluster/services/cluster/backends/native/dispatch.js +13 -0
  21. package/dist/src/lib/cluster/services/cluster/backends/native/index.js +526 -0
  22. package/dist/src/lib/cluster/services/cluster/backends/native/messaging.js +547 -0
  23. package/dist/src/lib/cluster/services/cluster/backends/state-utils.js +26 -0
  24. package/dist/src/lib/cluster/services/cluster/index.js +17 -0
  25. package/dist/src/lib/cluster/services/execution.js +435 -0
  26. package/dist/src/lib/cluster/services/index.js +6 -0
  27. package/dist/src/lib/cluster/services/interfaces.js +2 -0
  28. package/dist/src/lib/cluster/services/jobs.js +454 -0
  29. package/dist/src/lib/config/default-sysconfig.js +26 -0
  30. package/dist/src/lib/config/index.js +22 -0
  31. package/dist/src/lib/config/schemas/system.js +360 -0
  32. package/dist/src/lib/storage/analytics.js +86 -0
  33. package/dist/src/lib/storage/assets.js +401 -0
  34. package/dist/src/lib/storage/backends/elasticsearch_store.js +494 -0
  35. package/dist/src/lib/storage/backends/mappings/analytics.js +50 -0
  36. package/dist/src/lib/storage/backends/mappings/asset.js +41 -0
  37. package/dist/src/lib/storage/backends/mappings/ex.js +62 -0
  38. package/dist/src/lib/storage/backends/mappings/job.js +38 -0
  39. package/dist/src/lib/storage/backends/mappings/state.js +38 -0
  40. package/dist/src/lib/storage/backends/s3_store.js +237 -0
  41. package/dist/src/lib/storage/execution.js +300 -0
  42. package/dist/src/lib/storage/index.js +7 -0
  43. package/dist/src/lib/storage/jobs.js +81 -0
  44. package/dist/src/lib/storage/state.js +255 -0
  45. package/dist/src/lib/utils/api_utils.js +157 -0
  46. package/dist/src/lib/utils/asset_utils.js +94 -0
  47. package/dist/src/lib/utils/date_utils.js +52 -0
  48. package/dist/src/lib/utils/encoding_utils.js +27 -0
  49. package/dist/src/lib/utils/events.js +4 -0
  50. package/dist/src/lib/utils/file_utils.js +124 -0
  51. package/dist/src/lib/utils/id_utils.js +15 -0
  52. package/dist/src/lib/utils/port_utils.js +32 -0
  53. package/dist/src/lib/workers/assets/index.js +3 -0
  54. package/dist/src/lib/workers/assets/loader-executable.js +40 -0
  55. package/dist/src/lib/workers/assets/loader.js +73 -0
  56. package/dist/src/lib/workers/assets/spawn.js +55 -0
  57. package/dist/src/lib/workers/context/execution-context.js +12 -0
  58. package/dist/src/lib/workers/context/terafoundation-context.js +8 -0
  59. package/dist/src/lib/workers/execution-controller/execution-analytics.js +188 -0
  60. package/dist/src/lib/workers/execution-controller/index.js +1024 -0
  61. package/dist/src/lib/workers/execution-controller/recovery.js +151 -0
  62. package/dist/src/lib/workers/execution-controller/scheduler.js +390 -0
  63. package/dist/src/lib/workers/execution-controller/slice-analytics.js +96 -0
  64. package/dist/src/lib/workers/helpers/job.js +80 -0
  65. package/dist/src/lib/workers/helpers/op-analytics.js +22 -0
  66. package/dist/src/lib/workers/helpers/terafoundation.js +34 -0
  67. package/dist/src/lib/workers/helpers/worker-shutdown.js +169 -0
  68. package/dist/src/lib/workers/metrics/index.js +108 -0
  69. package/dist/src/lib/workers/worker/index.js +378 -0
  70. package/dist/src/lib/workers/worker/slice.js +122 -0
  71. package/dist/test/config/schemas/system_schema-spec.js +37 -0
  72. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8s-spec.js +316 -0
  73. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sResource-spec.js +795 -0
  74. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-multicluster-spec.js +67 -0
  75. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/k8sState-spec.js +84 -0
  76. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/utils-spec.js +132 -0
  77. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8s-v2-spec.js +455 -0
  78. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sResource-v2-spec.js +818 -0
  79. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-multicluster-v2-spec.js +67 -0
  80. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/k8sState-v2-spec.js +84 -0
  81. package/dist/test/lib/cluster/services/cluster/backends/kubernetes/v2/utils-v2-spec.js +320 -0
  82. package/dist/test/lib/cluster/services/cluster/backends/state-utils-spec.js +37 -0
  83. package/dist/test/node_master-spec.js +188 -0
  84. package/dist/test/services/api-spec.js +80 -0
  85. package/dist/test/services/assets-spec.js +158 -0
  86. package/dist/test/services/messaging-spec.js +440 -0
  87. package/dist/test/storage/assets_storage-spec.js +95 -0
  88. package/dist/test/storage/s3_store-spec.js +138 -0
  89. package/dist/test/test.config.js +8 -0
  90. package/dist/test/test.setup.js +6 -0
  91. package/dist/test/utils/api_utils-spec.js +86 -0
  92. package/dist/test/utils/asset_utils-spec.js +141 -0
  93. package/dist/test/utils/elastic_utils-spec.js +25 -0
  94. package/dist/test/workers/execution-controller/execution-controller-spec.js +371 -0
  95. package/dist/test/workers/execution-controller/execution-special-test-cases-spec.js +520 -0
  96. package/dist/test/workers/execution-controller/execution-test-cases-spec.js +338 -0
  97. package/dist/test/workers/execution-controller/recovery-spec.js +160 -0
  98. package/dist/test/workers/execution-controller/scheduler-spec.js +249 -0
  99. package/dist/test/workers/execution-controller/slice-analytics-spec.js +121 -0
  100. package/dist/test/workers/fixtures/ops/example-op/processor.js +20 -0
  101. package/dist/test/workers/fixtures/ops/example-op/schema.js +19 -0
  102. package/dist/test/workers/fixtures/ops/example-reader/fetcher.js +20 -0
  103. package/dist/test/workers/fixtures/ops/example-reader/schema.js +41 -0
  104. package/dist/test/workers/fixtures/ops/example-reader/slicer.js +37 -0
  105. package/dist/test/workers/fixtures/ops/new-op/processor.js +29 -0
  106. package/dist/test/workers/fixtures/ops/new-op/schema.js +18 -0
  107. package/dist/test/workers/fixtures/ops/new-reader/fetcher.js +19 -0
  108. package/dist/test/workers/fixtures/ops/new-reader/schema.js +23 -0
  109. package/dist/test/workers/fixtures/ops/new-reader/slicer.js +13 -0
  110. package/dist/test/workers/helpers/configs.js +130 -0
  111. package/dist/test/workers/helpers/execution-controller-helper.js +49 -0
  112. package/dist/test/workers/helpers/index.js +5 -0
  113. package/dist/test/workers/helpers/test-context.js +210 -0
  114. package/dist/test/workers/helpers/zip-directory.js +25 -0
  115. package/dist/test/workers/worker/slice-spec.js +333 -0
  116. package/dist/test/workers/worker/worker-spec.js +356 -0
  117. package/package.json +94 -94
  118. package/service.js +0 -0
@@ -0,0 +1,38 @@
1
+ export default {
2
+ template: '__state*',
3
+ mappings: {
4
+ state: {
5
+ _all: {
6
+ enabled: false
7
+ },
8
+ dynamic: false,
9
+ properties: {
10
+ ex_id: {
11
+ type: 'keyword'
12
+ },
13
+ slice_id: {
14
+ type: 'keyword'
15
+ },
16
+ slicer_id: {
17
+ type: 'keyword'
18
+ },
19
+ slicer_order: {
20
+ type: 'integer'
21
+ },
22
+ state: {
23
+ type: 'keyword'
24
+ },
25
+ _created: {
26
+ type: 'date'
27
+ },
28
+ _updated: {
29
+ type: 'date'
30
+ },
31
+ error: {
32
+ type: 'keyword'
33
+ }
34
+ }
35
+ }
36
+ }
37
+ };
38
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1,237 @@
1
+ import { TSError, isTest, logError, pDelay, pWhile, random } from '@terascope/utils';
2
+ import { S3ClientResponse, createS3Bucket, deleteS3Object, doesBucketExist, getS3Object, listS3Objects, putS3Object, s3RequestWithRetry } from '@terascope/file-asset-apis';
3
+ import { makeLogger } from '../../workers/helpers/terafoundation.js';
4
+ export class S3Store {
5
+ bucket;
6
+ connection;
7
+ terafoundation;
8
+ isShuttingDown;
9
+ logger;
10
+ api;
11
+ context;
12
+ constructor(backendConfig) {
13
+ const { context, terafoundation, connection, bucket, logger } = backendConfig;
14
+ this.context = context;
15
+ this.bucket = bucket || this.createDefaultBucketName();
16
+ this.connection = connection;
17
+ this.isShuttingDown = false;
18
+ this.logger = logger ?? makeLogger(context, 's3_backend', { storageName: this.bucket });
19
+ this.terafoundation = terafoundation;
20
+ }
21
+ async initialize() {
22
+ const { client } = await this.context.apis.foundation
23
+ .createClient({ type: 's3', cached: true, endpoint: this.connection });
24
+ this.api = client;
25
+ await pWhile(async () => {
26
+ try {
27
+ const exists = await doesBucketExist(client, { Bucket: this.bucket });
28
+ if (!exists) {
29
+ await createS3Bucket(client, { Bucket: this.bucket });
30
+ }
31
+ const isReady = await this.verifyClient();
32
+ return isReady;
33
+ }
34
+ catch (err) {
35
+ if (err.Code === 'InvalidAccessKeyId') {
36
+ throw new TSError(`accessKeyId ${this.terafoundation.connectors.s3[this.connection].accessKeyId} specified in S3 ${this.connection} does not exit: ${err.message}`);
37
+ }
38
+ if (err.Code === 'SignatureDoesNotMatch') {
39
+ throw new TSError(`secretAccessKey specified in S3 ${this.connection} does not match accessKeyId: ${err.message}`);
40
+ }
41
+ if (err.Code === 'InvalidBucketName') {
42
+ throw new TSError(`Bucket name does not follow S3 naming rules: ${err.message}`);
43
+ }
44
+ if (err instanceof S3ClientResponse.BucketAlreadyExists) {
45
+ throw new TSError(`Bucket name ${this.bucket} not available. accessKeyId ${this.terafoundation.connectors.s3[this.connection].accessKeyId} does not own this bucket. ${err.message}`);
46
+ }
47
+ logError(this.logger, err, `Failed attempt connecting to S3 ${this.connection} connection, ${this.bucket} bucket (will retry)`);
48
+ await pDelay(isTest ? 0 : random(2000, 4000));
49
+ return false;
50
+ }
51
+ });
52
+ }
53
+ // TODO: if we want to use the S3 store more generically we can't
54
+ // assume the key will have a '.zip' extension
55
+ async get(recordId) {
56
+ const command = {
57
+ Bucket: this.bucket,
58
+ Key: `${recordId}.zip`
59
+ };
60
+ try {
61
+ this.logger.debug(`getting record with id: ${recordId} from s3 ${this.connection} connection, ${this.bucket} bucket.`);
62
+ const client = this.api;
63
+ const bufferArray = [];
64
+ let triggerReturn = false;
65
+ const response = await s3RequestWithRetry({
66
+ client,
67
+ func: getS3Object,
68
+ params: command
69
+ });
70
+ /// Convert the response body to a Node read stream
71
+ const s3Stream = response.Body;
72
+ /// Store the data coming into s3 into a buffer array
73
+ s3Stream.on('data', (chunk) => {
74
+ bufferArray.push(chunk);
75
+ });
76
+ s3Stream.on('end', () => {
77
+ triggerReturn = true;
78
+ });
79
+ s3Stream.on('error', (err) => {
80
+ throw new TSError(`Unable to get recordId ${recordId} from s3 ${this.connection} connection, ${this.bucket} bucket.
81
+ Reason: ${err.message}`);
82
+ });
83
+ await pWhile(async () => triggerReturn);
84
+ return Buffer.concat(bufferArray);
85
+ }
86
+ catch (err) {
87
+ if (err instanceof S3ClientResponse.NoSuchKey) {
88
+ throw new TSError(`recordId ${recordId} does not exist in s3 ${this.connection} connection, ${this.bucket} bucket.`, {
89
+ statusCode: 404
90
+ });
91
+ }
92
+ throw new TSError(`Retrieval of recordId ${recordId} from s3 ${this.connection} connection, ${this.bucket} bucket failed: `, err);
93
+ }
94
+ }
95
+ async save(recordId, data, timeout) {
96
+ try {
97
+ this.logger.debug(`saving recordId ${recordId} to s3 ${this.connection} connection, ${this.bucket} bucket.`);
98
+ const command = {
99
+ Bucket: this.bucket,
100
+ Key: `${recordId}.zip`,
101
+ Body: data
102
+ };
103
+ const timeoutID = setTimeout(() => {
104
+ throw new TSError(`Timeout saving recordId ${recordId}`);
105
+ }, timeout);
106
+ try {
107
+ const client = this.api;
108
+ await s3RequestWithRetry({
109
+ client,
110
+ func: putS3Object,
111
+ params: command
112
+ });
113
+ }
114
+ catch (err) {
115
+ throw new TSError(`Failure saving recordId ${recordId} to S3: ${err}`);
116
+ }
117
+ finally {
118
+ clearTimeout(timeoutID);
119
+ }
120
+ }
121
+ catch (err) {
122
+ throw new TSError(`Error saving recordId ${recordId} to S3 ${this.connection} connection, ${this.bucket} bucket: ${err}`);
123
+ }
124
+ }
125
+ async remove(recordId) {
126
+ try {
127
+ this.logger.debug(`removing record ${recordId} from s3 ${this.connection} connection, ${this.bucket} bucket.`);
128
+ const command = {
129
+ Bucket: this.bucket,
130
+ Key: `${recordId}.zip`
131
+ };
132
+ const client = this.api;
133
+ await s3RequestWithRetry({
134
+ client,
135
+ func: deleteS3Object,
136
+ params: command
137
+ });
138
+ }
139
+ catch (err) {
140
+ throw new TSError(`Error deleting recordId ${recordId} from s3 ${this.connection} connection, ${this.bucket} bucket: ${err}`);
141
+ }
142
+ }
143
+ async list() {
144
+ /// list all keys in bucket
145
+ const objectList = [];
146
+ let nextContinuationToken;
147
+ let continuePagination = true;
148
+ try {
149
+ do {
150
+ const command = {
151
+ Bucket: this.bucket,
152
+ ContinuationToken: nextContinuationToken || undefined,
153
+ // MaxKeys: 1000 // Default is 1000
154
+ };
155
+ const client = this.api;
156
+ const response = await s3RequestWithRetry({
157
+ client,
158
+ func: listS3Objects,
159
+ params: command
160
+ });
161
+ response.Contents?.forEach((c) => {
162
+ const s3Record = {
163
+ File: c.Key,
164
+ Size: c.Size,
165
+ // Created: c.LastModified
166
+ };
167
+ objectList.push(s3Record);
168
+ });
169
+ if (!response.IsTruncated) {
170
+ continuePagination = false;
171
+ nextContinuationToken = undefined;
172
+ }
173
+ else {
174
+ nextContinuationToken = response.NextContinuationToken;
175
+ }
176
+ const recordsReceived = response.Contents?.length || 0;
177
+ this.logger.debug(`Received ${recordsReceived} records from s3 ${this.connection} connection, ${this.bucket} bucket.`);
178
+ } while (continuePagination);
179
+ this.logger.info(`Found ${objectList.length} records in s3 ${this.connection} connection, ${this.bucket} bucket.`);
180
+ }
181
+ catch (err) {
182
+ throw new TSError(`Error listing records from s3 ${this.connection} connection, ${this.bucket} bucket: ${err}`);
183
+ }
184
+ return objectList;
185
+ }
186
+ /*
187
+ * The S3 client has no built in functionality to determine if the client is connected.
188
+ * If we can make a request to ListS3Objects then we know that the bucket exists and
189
+ * credentials are valid.
190
+ */
191
+ async verifyClient() {
192
+ if (this.isShuttingDown)
193
+ return false;
194
+ const command = {
195
+ Bucket: this.bucket
196
+ };
197
+ const config = this.terafoundation.connectors.s3[this.connection];
198
+ try {
199
+ const client = this.api;
200
+ await s3RequestWithRetry({
201
+ client,
202
+ func: listS3Objects,
203
+ params: command
204
+ });
205
+ this.logger.debug(`S3 Client verification succeeded. Connection: ${this.connection}, endpoint: ${config.endpoint}`);
206
+ return true;
207
+ }
208
+ catch (err) {
209
+ this.logger.debug(`S3 Client verification failed. Connection: ${this.connection}, endpoint: ${config.endpoint}: `, err);
210
+ return false;
211
+ }
212
+ }
213
+ async waitForClient() {
214
+ this.logger.debug('waiting for s3 client');
215
+ if (await this.verifyClient())
216
+ return;
217
+ await pWhile(async () => {
218
+ if (this.isShuttingDown)
219
+ throw new Error('S3 store is shutdown');
220
+ if (await this.verifyClient())
221
+ return true;
222
+ await pDelay(100);
223
+ return false;
224
+ });
225
+ }
226
+ async shutdown() {
227
+ this.isShuttingDown = true;
228
+ this.api.destroy();
229
+ }
230
+ // TODO: Use more generic bucket prefix (not related to assets)
231
+ // or pass in prefix from calling class
232
+ createDefaultBucketName() {
233
+ const safeName = this.context.sysconfig.teraslice.name.replaceAll('_', '-');
234
+ return `ts-assets-${safeName}`;
235
+ }
236
+ }
237
+ //# sourceMappingURL=s3_store.js.map
@@ -0,0 +1,300 @@
1
+ import { TSError, includes, getTypeOf, makeISODate } from '@terascope/utils';
2
+ import { RecoveryCleanupType } from '@terascope/job-components';
3
+ import { v4 as uuid } from 'uuid';
4
+ import { makeLogger } from '../workers/helpers/terafoundation.js';
5
+ import { TerasliceElasticsearchStorage } from './backends/elasticsearch_store.js';
6
+ const INIT_STATUS = ['pending', 'scheduling', 'initializing'];
7
+ const RUNNING_STATUS = ['recovering', 'running', 'failing', 'paused', 'stopping'];
8
+ const TERMINAL_STATUS = ['completed', 'stopped', 'rejected', 'failed', 'terminated'];
9
+ const VALID_STATUS = INIT_STATUS.concat(RUNNING_STATUS).concat(TERMINAL_STATUS);
10
+ // Module to manager job states in Elasticsearch.
11
+ // All functions in this module return promises that must be resolved to
12
+ // get the final result.
13
+ export class ExecutionStorage {
14
+ backend;
15
+ jobType;
16
+ logger;
17
+ constructor(context) {
18
+ const logger = makeLogger(context, 'ex_storage');
19
+ const config = context.sysconfig.teraslice;
20
+ const jobType = 'ex';
21
+ const indexName = `${config.name}__ex`;
22
+ const backendConfig = {
23
+ context,
24
+ indexName,
25
+ recordType: 'ex',
26
+ idField: 'ex_id',
27
+ fullResponse: false,
28
+ logRecord: false,
29
+ storageName: 'execution',
30
+ logger
31
+ };
32
+ this.jobType = jobType;
33
+ this.logger = logger;
34
+ this.backend = new TerasliceElasticsearchStorage(backendConfig);
35
+ if (context.apis.executionContext) {
36
+ context.apis.executionContext.registerMetadataFns({ get: this.getMetadata.bind(this), update: this.updateMetadata.bind(this) });
37
+ }
38
+ }
39
+ async initialize() {
40
+ await this.backend.initialize();
41
+ this.logger.info('execution storage initialized');
42
+ }
43
+ async get(exId) {
44
+ const results = await this.backend.get(exId);
45
+ return results;
46
+ }
47
+ // encompasses all executions in either initialization or running statuses
48
+ async getActiveExecution(exId) {
49
+ const str = this.getTerminalStatuses().map((state) => ` _status:${state} `)
50
+ .join('OR');
51
+ const query = `ex_id:"${exId}" NOT (${str.trim()})`;
52
+ const executions = await this.backend.search(query, undefined, 1, '_created:desc');
53
+ if (!executions.length) {
54
+ throw new TSError(`no active execution context was found for ex_id: ${exId}`, {
55
+ statusCode: 404
56
+ });
57
+ }
58
+ return executions[0];
59
+ }
60
+ async search(query, from, size, sort, fields) {
61
+ const results = await this.backend.search(query, from, size, sort, fields);
62
+ return results;
63
+ }
64
+ async create(record, status = 'pending') {
65
+ if (!this._isValidStatus(status)) {
66
+ throw new Error(`Unknown status "${status}" on execution create`);
67
+ }
68
+ if (!record.job_id) {
69
+ throw new Error('Missing job_id on execution create');
70
+ }
71
+ const date = makeISODate();
72
+ const doc = Object.assign({}, record, {
73
+ ex_id: uuid(),
74
+ metadata: {},
75
+ _status: status,
76
+ _context: this.jobType,
77
+ _created: date,
78
+ _updated: date,
79
+ _has_errors: false,
80
+ _slicer_stats: {},
81
+ _failureReason: ''
82
+ });
83
+ // @ts-expect-error
84
+ delete doc.slicer_port;
85
+ // @ts-expect-error
86
+ delete doc.slicer_hostname;
87
+ try {
88
+ await this.backend.create(doc);
89
+ }
90
+ catch (err) {
91
+ throw new TSError(err, {
92
+ reason: 'Failure to create execution context'
93
+ });
94
+ }
95
+ return doc;
96
+ }
97
+ async updatePartial(exId, applyChanges) {
98
+ return this.backend.updatePartial(exId, applyChanges);
99
+ }
100
+ /**
101
+ * @typedef ExErrorMetadata
102
+ * @property _has_errors {boolean}
103
+ * @property _failureReason {string}
104
+ * @property _slicer_stats {import(
105
+ * '../workers/execution-controller/execution-analytics.js'
106
+ * ).ExecutionStats}
107
+ */
108
+ /**
109
+ * Format the execution error stats, primarly used for updating the
110
+ * status.
111
+ *
112
+ * If no error message is passed, it will reset the _has_errors and _failureReason.
113
+ * If execution stats is provided it will set the _slicer_stats
114
+ *
115
+ * @param stats {import(
116
+ * '../workers/execution-controller/execution-analytics.js'
117
+ * ).ExecutionStats=}
118
+ * @param errMsg {string=}
119
+ * @return {ExErrorMetadata}
120
+ */
121
+ // TODO: type out stats
122
+ executionMetaData(stats, errMsg) {
123
+ const errMetadata = {
124
+ _has_errors: false,
125
+ _failureReason: ''
126
+ };
127
+ const statsMetadata = {};
128
+ if (errMsg) {
129
+ errMetadata._has_errors = true;
130
+ errMetadata._failureReason = errMsg;
131
+ }
132
+ if (stats) {
133
+ statsMetadata._slicer_stats = Object.assign({}, stats);
134
+ }
135
+ return Object.assign({}, errMetadata, statsMetadata);
136
+ }
137
+ async getMetadata(exId) {
138
+ const ex = await this.get(exId);
139
+ return ex.metadata ?? {};
140
+ }
141
+ // TODO: type this
142
+ async updateMetadata(exId, metadata = {}) {
143
+ await this.backend.update(exId, {
144
+ metadata,
145
+ _updated: makeISODate()
146
+ });
147
+ }
148
+ // TODO: put a type of return
149
+ async getStatus(exId) {
150
+ try {
151
+ const result = await this.get(exId);
152
+ return result._status;
153
+ }
154
+ catch (err) {
155
+ throw new TSError(err, {
156
+ reason: `Cannot get execution status ${exId}`
157
+ });
158
+ }
159
+ }
160
+ // TODO: type this
161
+ // verify the current status to make sure it can be updated to the desired status
162
+ async verifyStatusUpdate(exId, desiredStatus) {
163
+ if (!desiredStatus || !this._isValidStatus(desiredStatus)) {
164
+ throw new TSError(`Invalid Job status: "${desiredStatus}"`, {
165
+ statusCode: 422
166
+ });
167
+ }
168
+ const status = await this.getStatus(exId);
169
+ this._verifyStatus(status, desiredStatus);
170
+ }
171
+ _verifyStatus(status, desiredStatus) {
172
+ // when setting the same status to shouldn't throw an error
173
+ if (desiredStatus === status) {
174
+ return status;
175
+ }
176
+ // when the current status is running it cannot be set to an init status
177
+ if (this._isRunningStatus(status) && this._isInitStatus(desiredStatus)) {
178
+ throw new TSError(`Cannot update running job status of "${status}" to init status of "${desiredStatus}"`, {
179
+ statusCode: 422
180
+ });
181
+ }
182
+ // if it is set to stop but the execution finishes before it can stop
183
+ // it is okay to set it to completed
184
+ if (status === 'stopped' && desiredStatus === 'completed') {
185
+ return status;
186
+ }
187
+ // when the status is a terminal status, it cannot be set to again
188
+ if (this._isTerminalStatus(status)) {
189
+ throw new TSError(`Cannot update terminal job status of "${status}" to "${desiredStatus}"`, {
190
+ statusCode: 422
191
+ });
192
+ }
193
+ // otherwise allow the update
194
+ return status;
195
+ }
196
+ // TODO: type this
197
+ /**
198
+ * Set the status
199
+ *
200
+ * @param {string} exId
201
+ * @param {string} status
202
+ * @param {Partial<import('@terascope/job-components').ExecutionConfig>} body
203
+ * @returns {Promise<import('@terascope/job-components').ExecutionConfig>}
204
+ */
205
+ async setStatus(exId, status, body) {
206
+ try {
207
+ return await this.updatePartial(exId, async (existing) => {
208
+ this._verifyStatus(existing._status, status);
209
+ return Object.assign(existing, body, {
210
+ _status: status,
211
+ _updated: makeISODate()
212
+ });
213
+ });
214
+ }
215
+ catch (err) {
216
+ throw new TSError(err, {
217
+ statusCode: 422,
218
+ reason: `Unable to set execution ${exId} status code to ${status}`
219
+ });
220
+ }
221
+ }
222
+ async softDelete(exId) {
223
+ try {
224
+ const date = makeISODate();
225
+ return await this.updatePartial(exId, async (existing) => Object.assign(existing, {
226
+ _deleted: true,
227
+ _deleted_on: date,
228
+ _updated: date
229
+ }));
230
+ }
231
+ catch (err) {
232
+ throw new TSError(err, {
233
+ statusCode: 422,
234
+ reason: `Unable to delete execution ${exId}`
235
+ });
236
+ }
237
+ }
238
+ async remove(exId) {
239
+ return this.backend.remove(exId);
240
+ }
241
+ async shutdown(forceShutdown) {
242
+ this.logger.info('shutting down.');
243
+ return this.backend.shutdown(forceShutdown);
244
+ }
245
+ verifyClient() {
246
+ return this.backend.verifyClient();
247
+ }
248
+ async waitForClient() {
249
+ return this.backend.waitForClient();
250
+ }
251
+ getTerminalStatuses() {
252
+ return TERMINAL_STATUS.slice();
253
+ }
254
+ getRunningStatuses() {
255
+ return RUNNING_STATUS.slice();
256
+ }
257
+ getLivingStatuses() {
258
+ return INIT_STATUS.concat(RUNNING_STATUS);
259
+ }
260
+ _isValidStatus(status) {
261
+ return includes(VALID_STATUS, status);
262
+ }
263
+ _isRunningStatus(status) {
264
+ return includes(RUNNING_STATUS, status);
265
+ }
266
+ _isTerminalStatus(status) {
267
+ return includes(TERMINAL_STATUS, status);
268
+ }
269
+ _isInitStatus(status) {
270
+ return includes(INIT_STATUS, status);
271
+ }
272
+ // TODO: fix types
273
+ /**
274
+ * @param {import('@terascope/job-components').ExecutionConfig} recoverFrom
275
+ * @param {RecoveryCleanupType} [cleanupType]
276
+ * @returns {Promise<import('@terascope/job-components').ExecutionConfig>}
277
+ */
278
+ async createRecoveredExecution(recoverFrom, cleanupType) {
279
+ if (!recoverFrom) {
280
+ throw new Error(`Invalid execution given, got ${getTypeOf(recoverFrom)}`);
281
+ }
282
+ if (!recoverFrom.ex_id) {
283
+ throw new Error('Unable to recover execution with missing ex_id');
284
+ }
285
+ const recoverFromId = recoverFrom.ex_id;
286
+ const ex = Object.assign({}, recoverFrom);
287
+ if (cleanupType && !RecoveryCleanupType[cleanupType]) {
288
+ throw new Error(`Unknown cleanup type "${cleanupType}" to recover`);
289
+ }
290
+ ex.recovered_execution = recoverFromId;
291
+ if (cleanupType) {
292
+ ex.recovered_slice_type = cleanupType;
293
+ }
294
+ else if (ex.autorecover) {
295
+ ex.recovered_slice_type = RecoveryCleanupType.pending;
296
+ }
297
+ return this.create(ex);
298
+ }
299
+ }
300
+ //# sourceMappingURL=execution.js.map
@@ -0,0 +1,7 @@
1
+ import { AnalyticsStorage } from './analytics.js';
2
+ import { AssetsStorage } from './assets.js';
3
+ import { ExecutionStorage } from './execution.js';
4
+ import { JobsStorage } from './jobs.js';
5
+ import { StateStorage, SliceState } from './state.js';
6
+ export { SliceState, AnalyticsStorage, AssetsStorage, ExecutionStorage, JobsStorage, StateStorage, };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,81 @@
1
+ import { v4 as uuid } from 'uuid';
2
+ import { TSError, makeISODate } from '@terascope/utils';
3
+ import { makeLogger } from '../workers/helpers/terafoundation.js';
4
+ import { TerasliceElasticsearchStorage } from './backends/elasticsearch_store.js';
5
+ export class JobsStorage {
6
+ backend;
7
+ jobType;
8
+ logger;
9
+ constructor(context) {
10
+ const logger = makeLogger(context, 'job_storage');
11
+ const config = context.sysconfig.teraslice;
12
+ const jobType = 'job';
13
+ const indexName = `${config.name}__jobs`;
14
+ const backendConfig = {
15
+ context,
16
+ indexName,
17
+ recordType: 'job',
18
+ idField: 'job_id',
19
+ fullResponse: false,
20
+ logRecord: false,
21
+ storageName: 'jobs',
22
+ logger
23
+ };
24
+ this.logger = logger;
25
+ this.jobType = jobType;
26
+ this.backend = new TerasliceElasticsearchStorage(backendConfig);
27
+ }
28
+ async initialize() {
29
+ await this.backend.initialize();
30
+ this.logger.info('job storage initialized');
31
+ }
32
+ async get(jobId) {
33
+ const doc = await this.backend.get(jobId);
34
+ return doc;
35
+ }
36
+ async search(query, from, size, sort, fields) {
37
+ const results = await this.backend.search(query, from, size, sort, fields);
38
+ return results;
39
+ }
40
+ async create(record) {
41
+ const date = makeISODate();
42
+ const doc = Object.assign({}, record, {
43
+ job_id: uuid(),
44
+ _context: this.jobType,
45
+ _created: date,
46
+ _updated: date
47
+ });
48
+ try {
49
+ await this.backend.create(doc);
50
+ }
51
+ catch (err) {
52
+ throw new TSError(err, {
53
+ reason: 'Failure to create job'
54
+ });
55
+ }
56
+ return doc;
57
+ }
58
+ async update(jobId, updateSpec) {
59
+ // We want to save the whole job as it is posted, update api does partial doc updates
60
+ const results = await this.backend.indexWithId(jobId, Object.assign({}, updateSpec, {
61
+ job_id: jobId,
62
+ _context: this.jobType,
63
+ _updated: makeISODate()
64
+ }));
65
+ return results;
66
+ }
67
+ async remove(jobId) {
68
+ return this.backend.remove(jobId);
69
+ }
70
+ async shutdown(forceShutdown) {
71
+ this.logger.info('shutting down.');
72
+ return this.backend.shutdown(forceShutdown);
73
+ }
74
+ verifyClient() {
75
+ return this.backend.verifyClient();
76
+ }
77
+ async waitForClient() {
78
+ return this.backend.waitForClient();
79
+ }
80
+ }
81
+ //# sourceMappingURL=jobs.js.map