@cumulus/async-operations 12.0.0 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/async_operations.d.ts +2 -0
- package/dist/async_operations.d.ts.map +1 -1
- package/dist/async_operations.js +45 -12
- package/dist/async_operations.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/src/async_operations.ts +45 -11
- package/tests/test-async_operations.js +79 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cumulus/async-operations",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"description": "Cumulus Core internal async operations module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -28,18 +28,18 @@
|
|
|
28
28
|
"author": "Cumulus Authors",
|
|
29
29
|
"license": "Apache-2.0",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@cumulus/aws-client": "
|
|
32
|
-
"@cumulus/db": "
|
|
33
|
-
"@cumulus/errors": "
|
|
34
|
-
"@cumulus/es-client": "
|
|
35
|
-
"@cumulus/types": "
|
|
31
|
+
"@cumulus/aws-client": "13.0.0",
|
|
32
|
+
"@cumulus/db": "13.0.0",
|
|
33
|
+
"@cumulus/errors": "13.0.0",
|
|
34
|
+
"@cumulus/es-client": "13.0.0",
|
|
35
|
+
"@cumulus/types": "13.0.0",
|
|
36
36
|
"knex": "0.95.15",
|
|
37
37
|
"uuid": "8.3.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@cumulus/common": "
|
|
40
|
+
"@cumulus/common": "13.0.0",
|
|
41
41
|
"@types/aws-sdk": "2.7.0",
|
|
42
42
|
"@types/uuid": "^8.0.0"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "ba43ea3bfdfcacfff052770ed2fd357eb9dadb58"
|
|
45
45
|
}
|
package/src/async_operations.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
AsyncOperationPgModel,
|
|
10
10
|
createRejectableTransaction,
|
|
11
11
|
} from '@cumulus/db';
|
|
12
|
+
import Logger from '@cumulus/logger';
|
|
12
13
|
import { ApiAsyncOperation, AsyncOperationType } from '@cumulus/types/api/async_operations';
|
|
13
14
|
import { v4 as uuidv4 } from 'uuid';
|
|
14
15
|
import type { AWSError } from 'aws-sdk/lib/error';
|
|
@@ -27,6 +28,8 @@ const {
|
|
|
27
28
|
Search,
|
|
28
29
|
} = require('@cumulus/es-client/search');
|
|
29
30
|
|
|
31
|
+
const logger = new Logger({ sender: '@cumulus/async-operation' });
|
|
32
|
+
|
|
30
33
|
type StartEcsTaskReturnType = Promise<PromiseResult<ECS.RunTaskResponse, AWSError>>;
|
|
31
34
|
|
|
32
35
|
export const getLambdaConfiguration = async (
|
|
@@ -198,6 +201,7 @@ export const createAsyncOperation = async (
|
|
|
198
201
|
*/
|
|
199
202
|
export const startAsyncOperation = async (
|
|
200
203
|
params: {
|
|
204
|
+
asyncOperationId?: string,
|
|
201
205
|
asyncOperationTaskDefinition: string,
|
|
202
206
|
cluster: string,
|
|
203
207
|
description: string,
|
|
@@ -230,7 +234,7 @@ export const startAsyncOperation = async (
|
|
|
230
234
|
throw new MissingRequiredArgument(`callerLambdaName must be specified to start new async operation, received: ${callerLambdaName}`);
|
|
231
235
|
}
|
|
232
236
|
|
|
233
|
-
const id = uuidv4();
|
|
237
|
+
const id = params.asyncOperationId ?? uuidv4();
|
|
234
238
|
// Store the payload to S3
|
|
235
239
|
const payloadBucket = systemBucket;
|
|
236
240
|
const payloadKey = `${stackName}/async-operation-payloads/${id}.json`;
|
|
@@ -241,20 +245,50 @@ export const startAsyncOperation = async (
|
|
|
241
245
|
Body: JSON.stringify(payload),
|
|
242
246
|
});
|
|
243
247
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
logger.debug(`About to start AsyncOperation: ${id}`);
|
|
249
|
+
let runTaskResponse;
|
|
250
|
+
try {
|
|
251
|
+
runTaskResponse = await startEcsTaskFunc({
|
|
252
|
+
...params,
|
|
253
|
+
id,
|
|
254
|
+
payloadBucket,
|
|
255
|
+
payloadKey,
|
|
256
|
+
});
|
|
251
257
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
if (runTaskResponse?.failures && runTaskResponse.failures.length > 0) {
|
|
259
|
+
throw new EcsStartTaskError(
|
|
260
|
+
`Failed to start AsyncOperation: ${runTaskResponse.failures[0].reason}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
logger.error(`Failed to start AsyncOperation ${id}`, error);
|
|
265
|
+
const output = {
|
|
266
|
+
name: error.name,
|
|
267
|
+
message: error.message,
|
|
268
|
+
stack: error.stack,
|
|
269
|
+
};
|
|
270
|
+
await createAsyncOperation(
|
|
271
|
+
{
|
|
272
|
+
createObject: {
|
|
273
|
+
id,
|
|
274
|
+
status: 'RUNNER_FAILED',
|
|
275
|
+
output: JSON.stringify(output),
|
|
276
|
+
description,
|
|
277
|
+
operationType,
|
|
278
|
+
createdAt: Date.now(),
|
|
279
|
+
updatedAt: Date.now(),
|
|
280
|
+
},
|
|
281
|
+
stackName,
|
|
282
|
+
systemBucket,
|
|
283
|
+
dynamoTableName,
|
|
284
|
+
knexConfig,
|
|
285
|
+
},
|
|
286
|
+
AsyncOperation
|
|
255
287
|
);
|
|
288
|
+
throw error;
|
|
256
289
|
}
|
|
257
290
|
|
|
291
|
+
logger.debug(`About to create AsyncOperation record: ${id}`);
|
|
258
292
|
return createAsyncOperation(
|
|
259
293
|
{
|
|
260
294
|
createObject: {
|
|
@@ -207,8 +207,61 @@ test.serial('The AsyncOperation start method starts an ECS task with the correct
|
|
|
207
207
|
t.is(environmentOverrides.payloadUrl, `s3://${systemBucket}/${stackName}/async-operation-payloads/${id}.json`);
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
test.serial('The
|
|
211
|
-
const createSpy = sinon.spy((obj) =>
|
|
210
|
+
test.serial('The AsyncOperation start method starts an ECS task with the asyncOperationId passed in', async (t) => {
|
|
211
|
+
const createSpy = sinon.spy((obj) => obj);
|
|
212
|
+
const stubbedAsyncOperationsModel = class {
|
|
213
|
+
create = createSpy;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
stubbedEcsRunTaskParams = {};
|
|
217
|
+
stubbedEcsRunTaskResult = {
|
|
218
|
+
tasks: [{ taskArn: randomString() }],
|
|
219
|
+
failures: [],
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const asyncOperationId = uuidv4();
|
|
223
|
+
const asyncOperationTaskDefinition = randomString();
|
|
224
|
+
const cluster = randomString();
|
|
225
|
+
const callerLambdaName = randomString();
|
|
226
|
+
const lambdaName = randomString();
|
|
227
|
+
const payload = { x: randomString() };
|
|
228
|
+
const stackName = randomString();
|
|
229
|
+
|
|
230
|
+
const { id } = await startAsyncOperation({
|
|
231
|
+
asyncOperationId,
|
|
232
|
+
asyncOperationTaskDefinition,
|
|
233
|
+
cluster,
|
|
234
|
+
lambdaName,
|
|
235
|
+
callerLambdaName,
|
|
236
|
+
description: randomString(),
|
|
237
|
+
operationType: 'ES Index',
|
|
238
|
+
payload,
|
|
239
|
+
stackName,
|
|
240
|
+
dynamoTableName: dynamoTableName,
|
|
241
|
+
knexConfig: knexConfig,
|
|
242
|
+
systemBucket,
|
|
243
|
+
useLambdaEnvironmentVariables: true,
|
|
244
|
+
}, stubbedAsyncOperationsModel);
|
|
245
|
+
|
|
246
|
+
t.is(stubbedEcsRunTaskParams.cluster, cluster);
|
|
247
|
+
t.is(stubbedEcsRunTaskParams.taskDefinition, asyncOperationTaskDefinition);
|
|
248
|
+
t.is(stubbedEcsRunTaskParams.launchType, 'FARGATE');
|
|
249
|
+
|
|
250
|
+
const environmentOverrides = {};
|
|
251
|
+
stubbedEcsRunTaskParams.overrides.containerOverrides[0].environment.forEach((env) => {
|
|
252
|
+
environmentOverrides[env.name] = env.value;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
t.is(id, asyncOperationId);
|
|
256
|
+
t.is(environmentOverrides.asyncOperationId, asyncOperationId);
|
|
257
|
+
t.is(environmentOverrides.asyncOperationsTable, dynamoTableName);
|
|
258
|
+
t.is(environmentOverrides.lambdaName, lambdaName);
|
|
259
|
+
t.is(environmentOverrides.payloadUrl, `s3://${systemBucket}/${stackName}/async-operation-payloads/${asyncOperationId}.json`);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test.serial('The startAsyncOperation method throws error and calls database model create method '
|
|
263
|
+
+ 'when it is unable to create an ECS task', async (t) => {
|
|
264
|
+
const createSpy = sinon.spy((obj) => obj);
|
|
212
265
|
const stubbedAsyncOperationsModel = class {
|
|
213
266
|
create = createSpy;
|
|
214
267
|
};
|
|
@@ -219,7 +272,8 @@ test.serial('The startAsyncOperation method throws error if it is unable to crea
|
|
|
219
272
|
};
|
|
220
273
|
const stackName = randomString();
|
|
221
274
|
|
|
222
|
-
|
|
275
|
+
const asyncOperationParams = {
|
|
276
|
+
asyncOperationId: uuidv4(),
|
|
223
277
|
asyncOperationTaskDefinition: randomString(),
|
|
224
278
|
cluster: randomString(),
|
|
225
279
|
callerLambdaName: randomString(),
|
|
@@ -231,10 +285,30 @@ test.serial('The startAsyncOperation method throws error if it is unable to crea
|
|
|
231
285
|
dynamoTableName: dynamoTableName,
|
|
232
286
|
knexConfig: knexConfig,
|
|
233
287
|
systemBucket,
|
|
234
|
-
}
|
|
288
|
+
};
|
|
289
|
+
const expectedErrorThrown = {
|
|
235
290
|
instanceOf: EcsStartTaskError,
|
|
236
291
|
message: 'Failed to start AsyncOperation: out of cheese',
|
|
237
|
-
}
|
|
292
|
+
};
|
|
293
|
+
await t.throwsAsync(
|
|
294
|
+
startAsyncOperation(asyncOperationParams, stubbedAsyncOperationsModel),
|
|
295
|
+
expectedErrorThrown
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const spyCall = createSpy.getCall(0).args[0];
|
|
299
|
+
|
|
300
|
+
const expected = {
|
|
301
|
+
id: asyncOperationParams.asyncOperationId,
|
|
302
|
+
description: asyncOperationParams.description,
|
|
303
|
+
operationType: asyncOperationParams.operationType,
|
|
304
|
+
status: 'RUNNER_FAILED',
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
t.like(spyCall, expected);
|
|
308
|
+
t.deepEqual(omit(spyCall, ['createdAt', 'updatedAt', 'output']), expected);
|
|
309
|
+
t.is(spyCall.id, asyncOperationParams.asyncOperationId);
|
|
310
|
+
const output = JSON.parse(spyCall.output || {});
|
|
311
|
+
t.like(output, { name: 'EcsStartTaskError', message: expectedErrorThrown.message });
|
|
238
312
|
});
|
|
239
313
|
|
|
240
314
|
test('startAsyncOperation calls Dynamo model create method', async (t) => {
|