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,454 @@
|
|
|
1
|
+
import { TSError, uniq, cloneDeep, isEmpty, getTypeOf, isString, makeISODate, defaultsDeep } from '@terascope/utils';
|
|
2
|
+
import { JobValidator, parseName } from '@terascope/job-components';
|
|
3
|
+
import { makeLogger } from '../../workers/helpers/terafoundation.js';
|
|
4
|
+
import { spawnAssetLoader } from '../../workers/assets/spawn.js';
|
|
5
|
+
/**
|
|
6
|
+
* New execution result
|
|
7
|
+
* @typedef NewExecutionResult
|
|
8
|
+
* @property {string} job_id
|
|
9
|
+
* @property {string} ex_id
|
|
10
|
+
*/
|
|
11
|
+
export class JobsService {
|
|
12
|
+
context;
|
|
13
|
+
jobValidator;
|
|
14
|
+
logger;
|
|
15
|
+
jobsStorage;
|
|
16
|
+
executionStorage;
|
|
17
|
+
executionService;
|
|
18
|
+
constructor(context) {
|
|
19
|
+
this.context = context;
|
|
20
|
+
this.logger = makeLogger(context, 'jobs_service');
|
|
21
|
+
this.jobValidator = new JobValidator(context);
|
|
22
|
+
}
|
|
23
|
+
async initialize() {
|
|
24
|
+
this.logger.info('job service is initializing...');
|
|
25
|
+
const { executionStorage, jobsStorage } = this.context.stores;
|
|
26
|
+
if (jobsStorage == null || executionStorage == null) {
|
|
27
|
+
throw new Error('Missing required stores');
|
|
28
|
+
}
|
|
29
|
+
const { executionService } = this.context.services;
|
|
30
|
+
if (executionService == null) {
|
|
31
|
+
throw new Error('Missing required services');
|
|
32
|
+
}
|
|
33
|
+
this.jobsStorage = jobsStorage;
|
|
34
|
+
this.executionStorage = executionStorage;
|
|
35
|
+
this.executionService = executionService;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate the job spec
|
|
39
|
+
*
|
|
40
|
+
* @returns {Promise<import('@terascope/job-components').ValidatedJobConfig>}
|
|
41
|
+
*/
|
|
42
|
+
async _validateJobSpec(jobSpec) {
|
|
43
|
+
const parsedAssetJob = await this._ensureAssets(cloneDeep(jobSpec));
|
|
44
|
+
const validJob = await this.jobValidator.validateConfig(parsedAssetJob);
|
|
45
|
+
return validJob;
|
|
46
|
+
}
|
|
47
|
+
async submitJob(jobSpec, shouldRun) {
|
|
48
|
+
// @ts-expect-error
|
|
49
|
+
if (jobSpec.job_id) {
|
|
50
|
+
throw new TSError('Job cannot include a job_id on submit', {
|
|
51
|
+
statusCode: 422,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
this.addExternalPortsToJobSpec(jobSpec);
|
|
55
|
+
const validJob = await this._validateJobSpec(jobSpec);
|
|
56
|
+
// We don't create with the fully parsed validJob as it changes the asset names
|
|
57
|
+
// to their asset id which we don't want stored as at the job level
|
|
58
|
+
const job = await this.jobsStorage.create(jobSpec);
|
|
59
|
+
if (!shouldRun) {
|
|
60
|
+
return { job_id: job.job_id };
|
|
61
|
+
}
|
|
62
|
+
const jobRecord = Object.assign({}, jobSpec, validJob, {
|
|
63
|
+
job_id: job.job_id
|
|
64
|
+
});
|
|
65
|
+
return this.executionService.createExecutionContext(jobRecord);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Sets the `active` property on the job to `true` or `false`.
|
|
69
|
+
* @param {string} jobId
|
|
70
|
+
* @param {boolean} activeState
|
|
71
|
+
*/
|
|
72
|
+
async setActiveState(jobId, activeState) {
|
|
73
|
+
const job = await this.jobsStorage.get(jobId);
|
|
74
|
+
if (activeState === true) {
|
|
75
|
+
job.active = true;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
job.active = false;
|
|
79
|
+
}
|
|
80
|
+
this.logger.info(`Setting jobId: ${jobId} to active: ${activeState}`);
|
|
81
|
+
return this.updateJob(jobId, job);
|
|
82
|
+
}
|
|
83
|
+
async updateJob(jobId, jobSpec) {
|
|
84
|
+
const originalJob = await this.jobsStorage.get(jobId);
|
|
85
|
+
if (originalJob._deleted === true) {
|
|
86
|
+
throw new TSError(`Job ${jobId} has been deleted and cannot be updated.`, {
|
|
87
|
+
statusCode: 410
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// If job is switching from active to inactive job validation is skipped
|
|
91
|
+
// This allows for old jobs that are missing required resources to be marked inactive
|
|
92
|
+
if (originalJob.active !== false && jobSpec.active === false) {
|
|
93
|
+
this.logger.info(`Skipping job validation to set jobId ${jobId} as _inactive`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this.addExternalPortsToJobSpec(jobSpec);
|
|
97
|
+
await this._validateJobSpec(jobSpec);
|
|
98
|
+
}
|
|
99
|
+
return this.jobsStorage.update(jobId, Object.assign({}, jobSpec, {
|
|
100
|
+
_created: originalJob._created
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Start a Job
|
|
105
|
+
*
|
|
106
|
+
* @param {string} jobId
|
|
107
|
+
* @returns {Promise<NewExecutionResult>}
|
|
108
|
+
*/
|
|
109
|
+
async startJob(jobId) {
|
|
110
|
+
const activeExecution = await this._getActiveExecution(jobId, true);
|
|
111
|
+
// searching for an active execution, if there is then we reject
|
|
112
|
+
if (activeExecution) {
|
|
113
|
+
throw new TSError(`Job ${jobId} is currently running, cannot have the same job concurrently running`, {
|
|
114
|
+
statusCode: 409
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
let currentResources = await this.executionService.listResourcesForJobId(jobId);
|
|
118
|
+
if (currentResources.length > 0) {
|
|
119
|
+
currentResources = currentResources.flat();
|
|
120
|
+
const exIdsSet = new Set();
|
|
121
|
+
for (const resource of currentResources) {
|
|
122
|
+
exIdsSet.add(resource.metadata.labels['teraslice.terascope.io/exId']);
|
|
123
|
+
}
|
|
124
|
+
const exIdsArr = Array.from(exIdsSet);
|
|
125
|
+
const exIdsString = exIdsArr.join(', ');
|
|
126
|
+
throw new TSError(`There are orphaned resources for job: ${jobId}, exId: ${exIdsString}.
|
|
127
|
+
Please wait for Kubernetes to clean up orphaned resources.`);
|
|
128
|
+
}
|
|
129
|
+
const jobSpec = await this.jobsStorage.get(jobId);
|
|
130
|
+
if (jobSpec._deleted === true) {
|
|
131
|
+
throw new TSError(`Job ${jobId} has been deleted and cannot be started.`, {
|
|
132
|
+
statusCode: 410
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const validJob = await this._validateJobSpec(jobSpec);
|
|
136
|
+
if (validJob.autorecover) {
|
|
137
|
+
return this._recoverValidJob(validJob);
|
|
138
|
+
}
|
|
139
|
+
return this.executionService.createExecutionContext(validJob);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Recover a job using the valid configuration
|
|
143
|
+
*
|
|
144
|
+
* @private
|
|
145
|
+
* @param {import('@terascope/job-components').ValidatedJobConfig} validJob
|
|
146
|
+
* @param {import('@terascope/job-components').RecoveryCleanupType} [cleanupType]
|
|
147
|
+
* @returns {Promise<NewExecutionResult>}
|
|
148
|
+
*/
|
|
149
|
+
async _recoverValidJob(validJob, cleanupType) {
|
|
150
|
+
const recoverFrom = await this.getLatestExecution(validJob.job_id, undefined, true);
|
|
151
|
+
// if there isn't an execution and autorecover is true
|
|
152
|
+
// create a new execution else throw
|
|
153
|
+
if (!recoverFrom) {
|
|
154
|
+
if (validJob.autorecover) {
|
|
155
|
+
return this.executionService.createExecutionContext(validJob);
|
|
156
|
+
}
|
|
157
|
+
throw new TSError(`Job ${validJob.job_id} is missing an execution to recover from`, {
|
|
158
|
+
statusCode: 404
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (validJob.slicers !== recoverFrom.slicers) {
|
|
162
|
+
const changedFrom = `from ${recoverFrom.slicers} to ${validJob.slicers}`;
|
|
163
|
+
this.logger.warn(`recovery for job ${recoverFrom.job_id} changed slicers ${changedFrom}`);
|
|
164
|
+
}
|
|
165
|
+
return this.executionService.recoverExecution(
|
|
166
|
+
// apply the latest job config changes
|
|
167
|
+
defaultsDeep({}, validJob, recoverFrom), cleanupType);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Recover a job, applied the last changes to the prev execution
|
|
171
|
+
*
|
|
172
|
+
* @param {string} jobId
|
|
173
|
+
* @param {import('@terascope/job-components').RecoveryCleanupType} [cleanupType]
|
|
174
|
+
* @returns {Promise<NewExecutionResult>}
|
|
175
|
+
*/
|
|
176
|
+
async recoverJob(jobId, cleanupType) {
|
|
177
|
+
// we need to do validations since the job config could change between recovery
|
|
178
|
+
const jobSpec = await this.jobsStorage.get(jobId);
|
|
179
|
+
if (jobSpec._deleted === true) {
|
|
180
|
+
throw new TSError(`Job ${jobId} has been deleted and cannot be recovered.`, {
|
|
181
|
+
statusCode: 410
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const validJob = await this._validateJobSpec(jobSpec);
|
|
185
|
+
return this._recoverValidJob(validJob, cleanupType);
|
|
186
|
+
}
|
|
187
|
+
async pauseJob(jobId) {
|
|
188
|
+
const exId = await this.getLatestExecutionId(jobId);
|
|
189
|
+
return this.executionService.pauseExecution(exId);
|
|
190
|
+
}
|
|
191
|
+
async resumeJob(jobId) {
|
|
192
|
+
const exId = await this.getLatestExecutionId(jobId);
|
|
193
|
+
return this.executionService.resumeExecution(exId);
|
|
194
|
+
}
|
|
195
|
+
async softDeleteJob(jobId) {
|
|
196
|
+
const activeExecution = await this._getActiveExecution(jobId, true);
|
|
197
|
+
// searching for an active execution, if there is then we reject
|
|
198
|
+
if (activeExecution) {
|
|
199
|
+
throw new TSError(`Job ${jobId} is currently running, cannot delete a running job.`, {
|
|
200
|
+
statusCode: 409
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// This will return any orphaned resources in k8s clustering
|
|
204
|
+
// or an empty array in native clustering
|
|
205
|
+
let currentResources = await this.executionService.listResourcesForJobId(jobId);
|
|
206
|
+
if (currentResources.length > 0) {
|
|
207
|
+
currentResources = currentResources.flat();
|
|
208
|
+
const exIdsSet = new Set();
|
|
209
|
+
for (const resource of currentResources) {
|
|
210
|
+
exIdsSet.add(resource.metadata.labels['teraslice.terascope.io/exId']);
|
|
211
|
+
}
|
|
212
|
+
const exIdsArr = Array.from(exIdsSet);
|
|
213
|
+
const exIdsString = exIdsArr.join(', ');
|
|
214
|
+
this.logger.info(`There are orphaned resources for job: ${jobId}, exId: ${exIdsString}.\n`
|
|
215
|
+
+ 'Removing resources before job deletion.');
|
|
216
|
+
await Promise.all(exIdsArr
|
|
217
|
+
.map((exId) => this.executionService.stopExecution(exId, { force: true })));
|
|
218
|
+
}
|
|
219
|
+
const jobSpec = await this.jobsStorage.get(jobId);
|
|
220
|
+
if (jobSpec._deleted === true) {
|
|
221
|
+
throw new TSError(`Job ${jobId} has already been deleted.`, {
|
|
222
|
+
statusCode: 410
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
jobSpec._deleted = true;
|
|
226
|
+
jobSpec._deleted_on = makeISODate();
|
|
227
|
+
jobSpec.active = false;
|
|
228
|
+
const executions = await this.getAllExecutions(jobId, undefined, true);
|
|
229
|
+
for (const execution of executions) {
|
|
230
|
+
await this.executionService.softDeleteExecutionContext(execution.ex_id);
|
|
231
|
+
}
|
|
232
|
+
return this.jobsStorage.update(jobId, jobSpec);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get all executions related to a jobId
|
|
236
|
+
*
|
|
237
|
+
* @param {string} jobId
|
|
238
|
+
* @param {string} [query]
|
|
239
|
+
* @param {boolean=false} [allowZeroResults]
|
|
240
|
+
* @returns {Promise<import('@terascope/types').ExecutionConfig[]>}
|
|
241
|
+
*/
|
|
242
|
+
async getAllExecutions(jobId, query, allowZeroResults = false) {
|
|
243
|
+
if (!jobId || !isString(jobId)) {
|
|
244
|
+
throw new TSError(`Invalid job id, got ${getTypeOf(jobId)}`);
|
|
245
|
+
}
|
|
246
|
+
const executions = await this.executionStorage.search(query || `job_id: "${jobId}"`, undefined, undefined, '_created:desc');
|
|
247
|
+
if (!allowZeroResults && !executions.length) {
|
|
248
|
+
throw new TSError(`No executions were found for job ${jobId}`, {
|
|
249
|
+
statusCode: 404
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return executions;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get the latest execution
|
|
256
|
+
*
|
|
257
|
+
* @param {string} jobId
|
|
258
|
+
* @param {string} [query]
|
|
259
|
+
* @param {boolean=false} [allowZeroResults]
|
|
260
|
+
* @returns {Promise<import('@terascope/types').ExecutionConfig>}
|
|
261
|
+
*/
|
|
262
|
+
async getLatestExecution(jobId, query, allowZeroResults = false) {
|
|
263
|
+
if (!jobId || !isString(jobId)) {
|
|
264
|
+
throw new TSError(`Invalid job id, got ${getTypeOf(jobId)}`);
|
|
265
|
+
}
|
|
266
|
+
const ex = await this.executionStorage.search(query || `job_id: "${jobId}"`, undefined, 1, '_created:desc');
|
|
267
|
+
if (!allowZeroResults && !ex.length) {
|
|
268
|
+
throw new TSError(`No execution was found for job ${jobId}`, {
|
|
269
|
+
statusCode: 404
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return ex[0];
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get the job with the latest ex status
|
|
276
|
+
*
|
|
277
|
+
* @param {string} jobId
|
|
278
|
+
* @param {string} [fields]
|
|
279
|
+
* @returns {Promise<JobConfig>}
|
|
280
|
+
*/
|
|
281
|
+
async getJobWithExInfo(jobId, fields) {
|
|
282
|
+
if (!jobId || !isString(jobId)) {
|
|
283
|
+
throw new TSError(`Invalid job id, got ${getTypeOf(jobId)}`);
|
|
284
|
+
}
|
|
285
|
+
const job = await this.jobsStorage.get(jobId);
|
|
286
|
+
const ex = await this.executionStorage.search(`job_id: "${jobId}"`, undefined, 1, '_created:desc', fields || undefined);
|
|
287
|
+
if (!ex.length || ex[0]._deleted === true) {
|
|
288
|
+
job.ex = {};
|
|
289
|
+
return job;
|
|
290
|
+
}
|
|
291
|
+
job.ex = ex[0];
|
|
292
|
+
return job;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Get a list of jobs with the latest ex status for each one
|
|
296
|
+
*
|
|
297
|
+
* @param {string} query
|
|
298
|
+
* @param {number} from
|
|
299
|
+
* @param {number} size
|
|
300
|
+
* @param {string} sort
|
|
301
|
+
* @param {string} [ex_fields]
|
|
302
|
+
* @returns {Promise<JobConfig>}
|
|
303
|
+
*/
|
|
304
|
+
async getJobsWithExInfo(query, from, size, sort, ex_fields) {
|
|
305
|
+
const jobList = await this.jobsStorage.search(query, from, size, sort);
|
|
306
|
+
const finalList = [];
|
|
307
|
+
for (const job of jobList) {
|
|
308
|
+
finalList.push(await this.getJobWithExInfo(job.job_id, ex_fields));
|
|
309
|
+
}
|
|
310
|
+
return finalList;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get the active execution
|
|
314
|
+
*
|
|
315
|
+
* @param {string} jobId
|
|
316
|
+
* @param {boolean} [allowZeroResults]
|
|
317
|
+
* @returns {Promise<import('@terascope/types').ExecutionConfig>}
|
|
318
|
+
*/
|
|
319
|
+
async _getActiveExecution(jobId, allowZeroResults) {
|
|
320
|
+
const statuses = this.executionStorage
|
|
321
|
+
.getTerminalStatuses()
|
|
322
|
+
.map((state) => ` _status:"${state}"`)
|
|
323
|
+
.join(' OR ');
|
|
324
|
+
const query = `job_id:"${jobId}" AND _context:ex NOT (${statuses.trim()})`;
|
|
325
|
+
return this.getLatestExecution(jobId, query, allowZeroResults);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get the active execution
|
|
329
|
+
*
|
|
330
|
+
* @param {string} jobId
|
|
331
|
+
* @returns {Promise<string>}
|
|
332
|
+
*/
|
|
333
|
+
async _getActiveExecutionId(jobId) {
|
|
334
|
+
const { ex_id } = await this._getActiveExecution(jobId);
|
|
335
|
+
return ex_id;
|
|
336
|
+
}
|
|
337
|
+
async getLatestExecutionId(jobId) {
|
|
338
|
+
const { ex_id } = await this.getLatestExecution(jobId);
|
|
339
|
+
return ex_id;
|
|
340
|
+
}
|
|
341
|
+
async shutdown() {
|
|
342
|
+
}
|
|
343
|
+
async addWorkers(jobId, workerCount) {
|
|
344
|
+
const exId = await this._getActiveExecutionId(jobId);
|
|
345
|
+
return this.executionService.addWorkers(exId, workerCount);
|
|
346
|
+
}
|
|
347
|
+
async removeWorkers(jobId, workerCount) {
|
|
348
|
+
const exId = await this._getActiveExecutionId(jobId);
|
|
349
|
+
return this.executionService.removeWorkers(exId, workerCount);
|
|
350
|
+
}
|
|
351
|
+
async setWorkers(jobId, workerCount) {
|
|
352
|
+
const exId = await this._getActiveExecutionId(jobId);
|
|
353
|
+
return this.executionService.setWorkers(exId, workerCount);
|
|
354
|
+
}
|
|
355
|
+
async _ensureAssets(jobConfig) {
|
|
356
|
+
const jobAssets = uniq(jobConfig.assets || []);
|
|
357
|
+
if (isEmpty(jobAssets)) {
|
|
358
|
+
return cloneDeep(jobConfig);
|
|
359
|
+
}
|
|
360
|
+
// convert asset references to their id's
|
|
361
|
+
const assetIds = await spawnAssetLoader(jobAssets);
|
|
362
|
+
if (!assetIds.length) {
|
|
363
|
+
throw new Error(`no asset id's were found for assets: ${JSON.stringify(jobAssets)}`);
|
|
364
|
+
}
|
|
365
|
+
if (jobAssets.length !== assetIds.length) {
|
|
366
|
+
throw new Error(`job specified ${jobAssets.length} assets: ${jobConfig.assets} but only ${assetIds.length} where found, assets: ${assetIds}`);
|
|
367
|
+
}
|
|
368
|
+
// need to normalize asset identifiers to their id form
|
|
369
|
+
// but not mutate original job_spec
|
|
370
|
+
const parsedAssetJob = cloneDeep(jobConfig);
|
|
371
|
+
parsedAssetJob.assets = assetIds;
|
|
372
|
+
const assetMapping = new Map();
|
|
373
|
+
for (let i = 0; i < jobAssets.length; i++) {
|
|
374
|
+
assetMapping.set(jobAssets[i], assetIds[i]);
|
|
375
|
+
}
|
|
376
|
+
this.adjustNamesByAsset(parsedAssetJob, assetMapping);
|
|
377
|
+
return parsedAssetJob;
|
|
378
|
+
}
|
|
379
|
+
adjustNamesByAsset(jobConfig, dict) {
|
|
380
|
+
jobConfig.operations = jobConfig.operations.map((op) => {
|
|
381
|
+
if (op.api_name?.includes('@')) {
|
|
382
|
+
const { name, assetIdentifier, tag } = parseName(op.api_name);
|
|
383
|
+
const hashId = dict.get(assetIdentifier);
|
|
384
|
+
if (!hashId) {
|
|
385
|
+
throw new Error(`Invalid operation api_name for _op: ${name}, could not find the hashID for asset identifier ${assetIdentifier}`);
|
|
386
|
+
}
|
|
387
|
+
let hashedName = `${name}@${hashId}`;
|
|
388
|
+
if (tag) {
|
|
389
|
+
hashedName = `${hashedName}:${tag}`;
|
|
390
|
+
}
|
|
391
|
+
op.api_name = hashedName;
|
|
392
|
+
}
|
|
393
|
+
if (op._op.includes('@')) {
|
|
394
|
+
const { name, assetIdentifier } = parseName(op._op);
|
|
395
|
+
const hashId = dict.get(assetIdentifier);
|
|
396
|
+
if (!hashId) {
|
|
397
|
+
throw new Error(`Invalid operation name for _op: ${name}, could not find the hashID for asset identifier ${assetIdentifier}`);
|
|
398
|
+
}
|
|
399
|
+
op._op = `${name}@${hashId}`;
|
|
400
|
+
return op;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
return op;
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
if (jobConfig.apis) {
|
|
407
|
+
jobConfig.apis = jobConfig.apis.map((api) => {
|
|
408
|
+
if (api._name.includes('@')) {
|
|
409
|
+
const { name, assetIdentifier, tag } = parseName(api._name);
|
|
410
|
+
const hashId = dict.get(assetIdentifier);
|
|
411
|
+
if (!hashId) {
|
|
412
|
+
throw new Error(`Invalid api name for _name: ${name}, could not find the hashID for asset identifier ${assetIdentifier}`);
|
|
413
|
+
}
|
|
414
|
+
let hashedName = `${name}@${hashId}`;
|
|
415
|
+
if (tag) {
|
|
416
|
+
hashedName = `${hashedName}:${tag}`;
|
|
417
|
+
}
|
|
418
|
+
api._name = hashedName;
|
|
419
|
+
return api;
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
return api;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Automatically add external_ports to jobSpec if needed.
|
|
429
|
+
* This ensures that the Prometheus exporter server can be scraped.
|
|
430
|
+
* Check if prom_metrics_enabled is true on jobSpec or teraslice config.
|
|
431
|
+
* If so, add or update external_ports property with correct port.
|
|
432
|
+
* @param {Partial<JobConfig>} jobSpec
|
|
433
|
+
*/
|
|
434
|
+
addExternalPortsToJobSpec(jobSpec) {
|
|
435
|
+
const { prom_metrics_enabled: enabledInTF, prom_metrics_port: tfPort } = this.context.sysconfig.terafoundation;
|
|
436
|
+
const { prom_metrics_enabled: enabledInJob, prom_metrics_port: jobPort } = jobSpec;
|
|
437
|
+
const portToUse = jobPort || tfPort;
|
|
438
|
+
if (enabledInJob === true || (enabledInJob === undefined && enabledInTF)) {
|
|
439
|
+
let portPresent = false;
|
|
440
|
+
if (!jobSpec.external_ports) {
|
|
441
|
+
jobSpec.external_ports = [];
|
|
442
|
+
}
|
|
443
|
+
for (const item of jobSpec.external_ports) {
|
|
444
|
+
const currentPort = typeof item === 'number' ? item : item.port;
|
|
445
|
+
if (currentPort === portToUse)
|
|
446
|
+
portPresent = true;
|
|
447
|
+
}
|
|
448
|
+
if (!portPresent) {
|
|
449
|
+
jobSpec.external_ports.push({ name: 'metrics', port: portToUse });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
//# sourceMappingURL=jobs.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function getConnectors() {
|
|
2
|
+
const connectors = {
|
|
3
|
+
'elasticsearch-next': {
|
|
4
|
+
default: {
|
|
5
|
+
node: ['localhost:9200']
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
kafka: {
|
|
9
|
+
default: {
|
|
10
|
+
brokers: ['localhost:9092']
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
return connectors;
|
|
15
|
+
}
|
|
16
|
+
export default {
|
|
17
|
+
terafoundation: {
|
|
18
|
+
environment: 'development',
|
|
19
|
+
connectors: getConnectors()
|
|
20
|
+
},
|
|
21
|
+
teraslice: {
|
|
22
|
+
master: true,
|
|
23
|
+
name: 'teracluster'
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=default-sysconfig.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { get } from '@terascope/utils';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { formats } from '@terascope/job-components';
|
|
5
|
+
import { configSchema } from './schemas/system.js';
|
|
6
|
+
const filePath = fileURLToPath(new URL('.', import.meta.url));
|
|
7
|
+
export function clusterName(configFile) {
|
|
8
|
+
return get(configFile, 'teraslice.name', null);
|
|
9
|
+
}
|
|
10
|
+
export function getTerasliceConfig(sysconfig) {
|
|
11
|
+
return Object.assign({
|
|
12
|
+
name: 'teraslice',
|
|
13
|
+
default_config_file: path.join(filePath, 'default-sysconfig.js'),
|
|
14
|
+
config_schema: configSchema,
|
|
15
|
+
schema_formats: formats,
|
|
16
|
+
cluster_name: clusterName,
|
|
17
|
+
shutdownMessaging: false,
|
|
18
|
+
start_workers: false,
|
|
19
|
+
}, sysconfig);
|
|
20
|
+
}
|
|
21
|
+
export { formats, configSchema, };
|
|
22
|
+
//# sourceMappingURL=index.js.map
|