@twin.org/dataspace-data-plane-service 0.0.3-next.25 → 0.0.3-next.26
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/es/dataspaceDataPlaneRoutes.js +17 -10
- package/dist/es/dataspaceDataPlaneRoutes.js.map +1 -1
- package/dist/es/dataspaceDataPlaneService.js +288 -165
- package/dist/es/dataspaceDataPlaneService.js.map +1 -1
- package/dist/es/entities/activityLogDetails.js +0 -8
- package/dist/es/entities/activityLogDetails.js.map +1 -1
- package/dist/es/entities/activityTask.js.map +1 -1
- package/dist/es/models/IDataspaceDataPlaneServiceConfig.js.map +1 -1
- package/dist/types/dataspaceDataPlaneRoutes.d.ts +3 -3
- package/dist/types/dataspaceDataPlaneService.d.ts +2 -2
- package/dist/types/entities/activityLogDetails.d.ts +0 -4
- package/dist/types/entities/activityTask.d.ts +2 -2
- package/dist/types/models/IDataspaceDataPlaneServiceConfig.d.ts +5 -0
- package/docs/changelog.md +17 -0
- package/docs/open-api/spec.json +77 -109
- package/docs/reference/classes/ActivityLogDetails.md +0 -8
- package/docs/reference/classes/ActivityTask.md +1 -1
- package/docs/reference/classes/DataspaceDataPlaneService.md +3 -3
- package/docs/reference/functions/activityStreamNotify.md +2 -2
- package/docs/reference/interfaces/IDataspaceDataPlaneServiceConfig.md +14 -0
- package/locales/en.json +3 -2
- package/package.json +3 -3
|
@@ -4,7 +4,7 @@ import { ArrayHelper, BaseError, ComponentFactory, ConflictError, Converter, Gen
|
|
|
4
4
|
import { Blake2b } from "@twin.org/crypto";
|
|
5
5
|
import { DataTypeHelper, JsonSchemaHelper } from "@twin.org/data-core";
|
|
6
6
|
import { JsonLdDataTypes, JsonLdHelper, JsonLdProcessor } from "@twin.org/data-json-ld";
|
|
7
|
-
import { ActivityProcessingStatus, DataRequestType, DataspaceAppFactory, DataspaceContexts, DataspaceDataTypes, DataspaceTypes } from "@twin.org/dataspace-models";
|
|
7
|
+
import { ActivityProcessingStatus, ActivityTaskStatus, DataRequestType, DataspaceAppFactory, DataspaceContexts, DataspaceDataTypes, DataspaceTypes } from "@twin.org/dataspace-models";
|
|
8
8
|
import { EngineCoreFactory } from "@twin.org/engine-models";
|
|
9
9
|
import { ComparisonOperator, LogicalOperator } from "@twin.org/entity";
|
|
10
10
|
import { EntityStorageConnectorFactory } from "@twin.org/entity-storage-models";
|
|
@@ -70,6 +70,11 @@ export class DataspaceDataPlaneService {
|
|
|
70
70
|
* @internal
|
|
71
71
|
*/
|
|
72
72
|
_activityLogStatusCallbacks;
|
|
73
|
+
/**
|
|
74
|
+
* Track task handler registrations to avoid resetting worker pools on every task.
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
_registeredTaskTypes;
|
|
73
78
|
/**
|
|
74
79
|
* Task retention. -1 retain forever.
|
|
75
80
|
* @internal
|
|
@@ -80,6 +85,11 @@ export class DataspaceDataPlaneService {
|
|
|
80
85
|
* @internal
|
|
81
86
|
*/
|
|
82
87
|
_retainActivityLogsFor;
|
|
88
|
+
/**
|
|
89
|
+
* Retry count for failed tasks.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
_retryCount;
|
|
83
93
|
/**
|
|
84
94
|
* Clean up interval for activity logs.
|
|
85
95
|
* @internal
|
|
@@ -144,11 +154,13 @@ export class DataspaceDataPlaneService {
|
|
|
144
154
|
DataspaceProtocolDataTypes.registerRedirects();
|
|
145
155
|
DataspaceProtocolDataTypes.registerTypes();
|
|
146
156
|
this._activityLogStatusCallbacks = {};
|
|
157
|
+
this._registeredTaskTypes = [];
|
|
147
158
|
this._partitionContextIds = options?.partitionContextIds;
|
|
148
159
|
this._retainTasksFor =
|
|
149
160
|
DataspaceDataPlaneService._DEFAULT_RETAIN_INTERVAL * DataspaceDataPlaneService._MS_PER_MINUTE;
|
|
150
161
|
this._retainActivityLogsFor =
|
|
151
162
|
DataspaceDataPlaneService._DEFAULT_RETAIN_INTERVAL * DataspaceDataPlaneService._MS_PER_MINUTE;
|
|
163
|
+
this._retryCount = options?.config?.retryCount;
|
|
152
164
|
this._activityLogCleanUpInterval = DataspaceDataPlaneService._DEFAULT_CLEANUP_INTERVAL;
|
|
153
165
|
this._cleanUpProcessOngoing = false;
|
|
154
166
|
const validationErrors = [];
|
|
@@ -225,7 +237,7 @@ export class DataspaceDataPlaneService {
|
|
|
225
237
|
/**
|
|
226
238
|
* Notify an Activity.
|
|
227
239
|
* @param activity The Activity notified.
|
|
228
|
-
* @returns The
|
|
240
|
+
* @returns The activity's id or entry.
|
|
229
241
|
*/
|
|
230
242
|
async notifyActivity(activity) {
|
|
231
243
|
Guards.object(DataspaceDataPlaneService.CLASS_NAME, "activity", activity);
|
|
@@ -255,10 +267,11 @@ export class DataspaceDataPlaneService {
|
|
|
255
267
|
const activityLogId = Converter.bytesToHex(Blake2b.sum256(canonicalBytes));
|
|
256
268
|
const activityLogEntryId = `urn:x-activity-log:${activityLogId}`;
|
|
257
269
|
// Check if entry already exists
|
|
258
|
-
|
|
270
|
+
let logEntry = await this._entityStorageActivityLogs.get(activityLogEntryId);
|
|
259
271
|
let existingSuccessfulApps = [];
|
|
260
272
|
let isRetry = false;
|
|
261
|
-
|
|
273
|
+
const now = Date.now();
|
|
274
|
+
if (!Is.undefined(logEntry)) {
|
|
262
275
|
// Check if there are failed tasks that can be retried
|
|
263
276
|
const existingEntry = await this.getActivityLogEntry(activityLogEntryId);
|
|
264
277
|
// If all tasks completed successfully, this is a duplicate
|
|
@@ -276,69 +289,46 @@ export class DataspaceDataPlaneService {
|
|
|
276
289
|
isRetry = true;
|
|
277
290
|
}
|
|
278
291
|
else {
|
|
279
|
-
|
|
292
|
+
logEntry = {
|
|
280
293
|
id: activityLogEntryId,
|
|
281
|
-
activityId:
|
|
294
|
+
activityId: activity.id,
|
|
282
295
|
generator: this.calculateActivityGeneratorIdentity(activity),
|
|
283
|
-
dateCreated: new Date().toISOString(),
|
|
284
|
-
dateModified: new Date().toISOString()
|
|
296
|
+
dateCreated: new Date(now).toISOString(),
|
|
297
|
+
dateModified: new Date(now).toISOString()
|
|
285
298
|
};
|
|
286
299
|
await this._entityStorageActivityLogs.set(logEntry);
|
|
287
300
|
}
|
|
288
301
|
const activityQuerySet = await this.calculateActivityQuerySet(activity);
|
|
289
|
-
const
|
|
290
|
-
const
|
|
302
|
+
const taskEntries = [];
|
|
303
|
+
const handlerApps = {};
|
|
291
304
|
for (const query of activityQuerySet) {
|
|
292
|
-
const
|
|
293
|
-
for (const appId
|
|
294
|
-
|
|
295
|
-
|
|
305
|
+
const apps = this.getAppForActivityQuery(query);
|
|
306
|
+
for (const appId in apps) {
|
|
307
|
+
// Only process apps that haven't already completed successfully
|
|
308
|
+
if (!handlerApps[appId] && !existingSuccessfulApps.includes(appId)) {
|
|
309
|
+
handlerApps[appId] = apps[appId];
|
|
296
310
|
}
|
|
297
311
|
}
|
|
298
312
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
activityLogEntryId,
|
|
304
|
-
activity: activity,
|
|
305
|
-
executorApp: dataspaceAppId
|
|
306
|
-
};
|
|
307
|
-
const taskType = Converter.bytesToHex(RandomHelper.generate(16));
|
|
308
|
-
const taskId = await this._backgroundTaskComponent.create(taskType, payload, {
|
|
309
|
-
retainFor: this._retainTasksFor
|
|
310
|
-
});
|
|
311
|
-
await this._backgroundTaskComponent.registerHandler(taskType, "@twin.org/dataspace-app-runner", "appRunner", async (task) => {
|
|
312
|
-
await this.finaliseTask(task);
|
|
313
|
-
}, {
|
|
314
|
-
initialiseMethod: "appRunnerStart",
|
|
315
|
-
shutdownMethod: "appRunnerEnd"
|
|
316
|
-
});
|
|
317
|
-
tasksScheduled.push({
|
|
318
|
-
taskId,
|
|
319
|
-
dataspaceAppId
|
|
320
|
-
});
|
|
321
|
-
await this._logging?.log({
|
|
322
|
-
level: "info",
|
|
323
|
-
source: DataspaceDataPlaneService.CLASS_NAME,
|
|
324
|
-
message: "scheduledTask",
|
|
325
|
-
data: {
|
|
326
|
-
taskId,
|
|
327
|
-
dataspaceAppId,
|
|
328
|
-
isRetry
|
|
329
|
-
}
|
|
330
|
-
});
|
|
313
|
+
let inlineCount = 0;
|
|
314
|
+
for (const handlerAppId in handlerApps) {
|
|
315
|
+
if (await this.processTask(activityLogEntryId, activity, handlerApps, handlerAppId, taskEntries, isRetry)) {
|
|
316
|
+
inlineCount++;
|
|
331
317
|
}
|
|
332
318
|
}
|
|
333
319
|
const existingActivityTasks = isRetry
|
|
334
320
|
? await this._entityStorageActivityTasks.get(activityLogEntryId)
|
|
335
321
|
: undefined;
|
|
336
322
|
const existingTasksToKeep = existingActivityTasks?.associatedTasks.filter(t => existingSuccessfulApps.includes(t.dataspaceAppId)) ?? [];
|
|
337
|
-
|
|
323
|
+
const activityTask = {
|
|
338
324
|
activityLogEntryId,
|
|
339
|
-
associatedTasks: [...existingTasksToKeep, ...
|
|
340
|
-
}
|
|
341
|
-
|
|
325
|
+
associatedTasks: [...existingTasksToKeep, ...taskEntries]
|
|
326
|
+
};
|
|
327
|
+
await this._entityStorageActivityTasks.set(activityTask);
|
|
328
|
+
if (inlineCount === taskEntries.length) {
|
|
329
|
+
return this.finaliseActivityLogEntry(activityLogEntryId);
|
|
330
|
+
}
|
|
331
|
+
return activityTask.activityLogEntryId;
|
|
342
332
|
}
|
|
343
333
|
/**
|
|
344
334
|
* Subscribes to the activity log.
|
|
@@ -370,68 +360,12 @@ export class DataspaceDataPlaneService {
|
|
|
370
360
|
*/
|
|
371
361
|
async getActivityLogEntry(logEntryId) {
|
|
372
362
|
Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "logEntryId", logEntryId);
|
|
373
|
-
const
|
|
374
|
-
if (Is.undefined(
|
|
363
|
+
const activityLog = await this._entityStorageActivityLogs.get(logEntryId);
|
|
364
|
+
if (Is.undefined(activityLog)) {
|
|
375
365
|
throw new NotFoundError(DataspaceDataPlaneService.CLASS_NAME, "activityLogEntryNotFound", logEntryId);
|
|
376
366
|
}
|
|
377
|
-
let pendingTasks;
|
|
378
|
-
let runningTasks;
|
|
379
|
-
let finalizedTasks;
|
|
380
|
-
let inErrorTasks;
|
|
381
|
-
// For calculating the processing status. `Registering` if we cannot determine the activity tasks yet
|
|
382
|
-
let status = ActivityProcessingStatus.Registering;
|
|
383
|
-
// Now query the associated tasks
|
|
384
367
|
const activityTasks = await this._entityStorageActivityTasks.get(logEntryId);
|
|
385
|
-
|
|
386
|
-
if (!Is.undefined(activityTasks)) {
|
|
387
|
-
pendingTasks = [];
|
|
388
|
-
runningTasks = [];
|
|
389
|
-
finalizedTasks = [];
|
|
390
|
-
inErrorTasks = [];
|
|
391
|
-
for (const entity of activityTasks.associatedTasks) {
|
|
392
|
-
const taskDetails = await this._backgroundTaskComponent.get(entity.taskId);
|
|
393
|
-
if (Is.object(taskDetails)) {
|
|
394
|
-
switch (taskDetails.status) {
|
|
395
|
-
case TaskStatus.Success:
|
|
396
|
-
finalizedTasks.push({
|
|
397
|
-
...entity,
|
|
398
|
-
result: JSON.stringify(taskDetails.result),
|
|
399
|
-
startDate: taskDetails?.dateCreated,
|
|
400
|
-
endDate: taskDetails?.dateCompleted
|
|
401
|
-
});
|
|
402
|
-
break;
|
|
403
|
-
case TaskStatus.Pending:
|
|
404
|
-
pendingTasks.push(entity);
|
|
405
|
-
break;
|
|
406
|
-
case TaskStatus.Processing:
|
|
407
|
-
runningTasks.push({ ...entity, startDate: taskDetails.dateCreated });
|
|
408
|
-
break;
|
|
409
|
-
case TaskStatus.Failed:
|
|
410
|
-
inErrorTasks.push({
|
|
411
|
-
...entity,
|
|
412
|
-
error: taskDetails.error
|
|
413
|
-
});
|
|
414
|
-
break;
|
|
415
|
-
case TaskStatus.Cancelled:
|
|
416
|
-
// Nothing to do for cancelled tasks
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
if (Is.arrayValue(inErrorTasks)) {
|
|
422
|
-
status = ActivityProcessingStatus.Error;
|
|
423
|
-
}
|
|
424
|
-
else if (Is.arrayValue(runningTasks)) {
|
|
425
|
-
status = ActivityProcessingStatus.Running;
|
|
426
|
-
}
|
|
427
|
-
else if (Is.arrayValue(pendingTasks)) {
|
|
428
|
-
status = ActivityProcessingStatus.Pending;
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
status = ActivityProcessingStatus.Completed;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
return { ...result, status, pendingTasks, runningTasks, finalizedTasks, inErrorTasks };
|
|
368
|
+
return this.constructLogEntry(activityLog, activityTasks);
|
|
435
369
|
}
|
|
436
370
|
/**
|
|
437
371
|
* Get Data Asset entities. Allows to retrieve entities by their type or id.
|
|
@@ -471,9 +405,7 @@ export class DataspaceDataPlaneService {
|
|
|
471
405
|
}
|
|
472
406
|
const datasetId = serviceDataset["@id"];
|
|
473
407
|
Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "datasetId", datasetId);
|
|
474
|
-
const
|
|
475
|
-
// getAppForDataAssetQuery already validates app exists
|
|
476
|
-
const app = DataspaceAppFactory.get(appId);
|
|
408
|
+
const app = await this.getAppForDataAssetQuery({ datasetId });
|
|
477
409
|
const handleDataRequest = app.handleDataRequest?.bind(app);
|
|
478
410
|
Guards.function(DataspaceDataPlaneService.CLASS_NAME, "handleDataRequest", handleDataRequest);
|
|
479
411
|
const dataRequest = {
|
|
@@ -537,8 +469,7 @@ export class DataspaceDataPlaneService {
|
|
|
537
469
|
const serviceDataset = await this.getDatasetFromApps(resolvedDatasetId);
|
|
538
470
|
const datasetId = serviceDataset["@id"];
|
|
539
471
|
Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "datasetId", datasetId);
|
|
540
|
-
const
|
|
541
|
-
const app = DataspaceAppFactory.get(appId);
|
|
472
|
+
const app = await this.getAppForDataAssetQuery({ datasetId });
|
|
542
473
|
if (!app.supportedQueryTypes().includes(query.type)) {
|
|
543
474
|
throw new UnprocessableError(DataspaceDataPlaneService.CLASS_NAME, "queryTypeNotSupported", {
|
|
544
475
|
queryType: query.type
|
|
@@ -643,55 +574,65 @@ export class DataspaceDataPlaneService {
|
|
|
643
574
|
}
|
|
644
575
|
/**
|
|
645
576
|
* Process activity task finalization.
|
|
646
|
-
* @param
|
|
577
|
+
* @param taskId The Id of the Activity Log Entry.
|
|
578
|
+
* @param status The final status of the task.
|
|
579
|
+
* @param payload The execution payload of the task, required to correlate to the Activity Log Entry and update the processing status.
|
|
647
580
|
* @internal
|
|
648
581
|
*/
|
|
649
|
-
async
|
|
650
|
-
const payload = task.payload;
|
|
582
|
+
async finaliseBackgroundTask(taskId, status, payload) {
|
|
651
583
|
if (Is.empty(payload)) {
|
|
652
584
|
return;
|
|
653
585
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
await this.
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
586
|
+
if (status === TaskStatus.Success || status === TaskStatus.Failed) {
|
|
587
|
+
await this.notifyTaskStatusChanged(payload.activityLogEntryId, payload.activity.id, payload.dataspaceAppId, taskId, status);
|
|
588
|
+
await this.finaliseActivityLogEntry(payload.activityLogEntryId);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Notify registered callbacks about a task status change.
|
|
593
|
+
* @param activityLogEntryId The Id of the Activity Log Entry.
|
|
594
|
+
* @param activityId The Id of the Activity.
|
|
595
|
+
* @param dataspaceAppId The Id of the Dataspace App associated with the task.
|
|
596
|
+
* @param taskId The Id of the task.
|
|
597
|
+
* @param taskStatus The new status of the task.
|
|
598
|
+
* @internal
|
|
599
|
+
*/
|
|
600
|
+
async notifyTaskStatusChanged(activityLogEntryId, activityId, dataspaceAppId, taskId, taskStatus) {
|
|
601
|
+
for (const callback of Object.values(this._activityLogStatusCallbacks)) {
|
|
602
|
+
await callback({
|
|
603
|
+
activityLogEntryId,
|
|
604
|
+
activityId,
|
|
605
|
+
taskProcessingStatus: {
|
|
606
|
+
dataspaceAppId,
|
|
607
|
+
taskId,
|
|
608
|
+
taskStatus
|
|
662
609
|
}
|
|
663
610
|
});
|
|
664
611
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
dateCreated: entry.dateCreated,
|
|
688
|
-
dateModified: entry.dateModified,
|
|
689
|
-
retainUntil,
|
|
690
|
-
retryCount: entry.retryCount
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
}
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Finalizes the Activity Log Entry by checking if all associated tasks have completed and, if so, updating the entry to be retained for the configured retention period.
|
|
615
|
+
* @param activityLogEntryId The Id of the Activity Log Entry to finalize.
|
|
616
|
+
* @returns The Activity Log Entry with updated retention details if applicable.
|
|
617
|
+
* @internal
|
|
618
|
+
*/
|
|
619
|
+
async finaliseActivityLogEntry(activityLogEntryId) {
|
|
620
|
+
const entry = await this.getActivityLogEntry(activityLogEntryId);
|
|
621
|
+
if (this._retainActivityLogsFor !== -1 &&
|
|
622
|
+
(entry.status === ActivityProcessingStatus.Completed ||
|
|
623
|
+
entry.status === ActivityProcessingStatus.Error)) {
|
|
624
|
+
const retainUntil = Date.now() + this._retainActivityLogsFor;
|
|
625
|
+
const updatedEntry = {
|
|
626
|
+
id: entry.id,
|
|
627
|
+
activityId: entry.activityId,
|
|
628
|
+
generator: entry.generator,
|
|
629
|
+
dateCreated: entry.dateCreated,
|
|
630
|
+
dateModified: entry.dateModified,
|
|
631
|
+
retainUntil
|
|
632
|
+
};
|
|
633
|
+
await this._entityStorageActivityLogs.set(updatedEntry);
|
|
694
634
|
}
|
|
635
|
+
return entry;
|
|
695
636
|
}
|
|
696
637
|
/**
|
|
697
638
|
* Cleans up the activity log by deleting those entries that no longer shall be retained.
|
|
@@ -850,7 +791,7 @@ export class DataspaceDataPlaneService {
|
|
|
850
791
|
* @internal
|
|
851
792
|
*/
|
|
852
793
|
getAppForActivityQuery(activityQuery) {
|
|
853
|
-
const matchingElements =
|
|
794
|
+
const matchingElements = {};
|
|
854
795
|
const appNames = DataspaceAppFactory.names();
|
|
855
796
|
for (const appId of appNames) {
|
|
856
797
|
const app = DataspaceAppFactory.get(appId);
|
|
@@ -860,10 +801,10 @@ export class DataspaceDataPlaneService {
|
|
|
860
801
|
(Is.undefined(appQuery.activityType) ||
|
|
861
802
|
appQuery.activityType === activityQuery.activityType) &&
|
|
862
803
|
(Is.undefined(appQuery.targetType) || appQuery.targetType === activityQuery.targetType)) {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
}
|
|
804
|
+
matchingElements[appId] = {
|
|
805
|
+
app,
|
|
806
|
+
processingGroupId: appQuery.processingGroupId
|
|
807
|
+
};
|
|
867
808
|
}
|
|
868
809
|
}
|
|
869
810
|
}
|
|
@@ -899,18 +840,20 @@ export class DataspaceDataPlaneService {
|
|
|
899
840
|
*/
|
|
900
841
|
async getAppForDataAssetQuery(dataAssetQuery) {
|
|
901
842
|
const matchingElements = [];
|
|
843
|
+
const matchingIds = [];
|
|
902
844
|
const appNames = DataspaceAppFactory.names();
|
|
903
845
|
for (const appId of appNames) {
|
|
904
846
|
const app = DataspaceAppFactory.get(appId);
|
|
905
847
|
const datasets = await app.datasetsHandled();
|
|
906
848
|
for (const dataset of datasets) {
|
|
907
849
|
if (dataset["@id"] === dataAssetQuery.datasetId) {
|
|
908
|
-
matchingElements.push(
|
|
850
|
+
matchingElements.push(app);
|
|
851
|
+
matchingIds.push(appId);
|
|
909
852
|
}
|
|
910
853
|
}
|
|
911
854
|
}
|
|
912
855
|
if (matchingElements.length > 1) {
|
|
913
|
-
const error = new ConflictError(DataspaceDataPlaneService.CLASS_NAME, "tooManyAppsRegistered", dataAssetQuery.datasetId,
|
|
856
|
+
const error = new ConflictError(DataspaceDataPlaneService.CLASS_NAME, "tooManyAppsRegistered", dataAssetQuery.datasetId, matchingIds, {
|
|
914
857
|
datasetId: dataAssetQuery.datasetId
|
|
915
858
|
});
|
|
916
859
|
await this._logging?.log({
|
|
@@ -949,11 +892,15 @@ export class DataspaceDataPlaneService {
|
|
|
949
892
|
* @internal
|
|
950
893
|
*/
|
|
951
894
|
async prepareForRetry(activityLogEntryId, existingEntry) {
|
|
952
|
-
const appsToRetry = existingEntry.
|
|
895
|
+
const appsToRetry = existingEntry.tasks
|
|
896
|
+
?.filter(t => t.status === ActivityTaskStatus.Failed)
|
|
897
|
+
.map(t => t.dataspaceAppId) ?? [];
|
|
953
898
|
if (!Is.arrayValue(appsToRetry)) {
|
|
954
899
|
throw new NotFoundError(DataspaceDataPlaneService.CLASS_NAME, "noFailedTasksToRetry", activityLogEntryId);
|
|
955
900
|
}
|
|
956
|
-
const successfulApps = existingEntry.
|
|
901
|
+
const successfulApps = existingEntry.tasks
|
|
902
|
+
?.filter(t => t.status === ActivityTaskStatus.Success)
|
|
903
|
+
.map(t => t.dataspaceAppId) ?? [];
|
|
957
904
|
await this._logging?.log({
|
|
958
905
|
level: "debug",
|
|
959
906
|
source: DataspaceDataPlaneService.CLASS_NAME,
|
|
@@ -971,8 +918,6 @@ export class DataspaceDataPlaneService {
|
|
|
971
918
|
if (this._retainActivityLogsFor !== -1) {
|
|
972
919
|
logEntry.retainUntil = Date.now() + this._retainActivityLogsFor;
|
|
973
920
|
}
|
|
974
|
-
// Monitoring purposes
|
|
975
|
-
logEntry.retryCount = (logEntry.retryCount ?? 0) + 1;
|
|
976
921
|
await this._entityStorageActivityLogs.set(logEntry);
|
|
977
922
|
}
|
|
978
923
|
return successfulApps;
|
|
@@ -1068,5 +1013,183 @@ export class DataspaceDataPlaneService {
|
|
|
1068
1013
|
});
|
|
1069
1014
|
}
|
|
1070
1015
|
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Processes a task for an activity, by creating a background task and registering the handler.
|
|
1018
|
+
* @param activityLogEntryId The ID of the activity log entry.
|
|
1019
|
+
* @param activity The activity to be processed.
|
|
1020
|
+
* @param handlerApps The handler applications for the activity.
|
|
1021
|
+
* @param dataspaceAppId The ID of the handler application.
|
|
1022
|
+
* @param taskEntries The list of activity log entries.
|
|
1023
|
+
* @param isRetry Indicates if this is a retry of a previous task.
|
|
1024
|
+
* @returns True if the task was processed inline.
|
|
1025
|
+
* @internal
|
|
1026
|
+
*/
|
|
1027
|
+
async processTask(activityLogEntryId, activity, handlerApps, dataspaceAppId, taskEntries, isRetry) {
|
|
1028
|
+
const handlerApp = handlerApps[dataspaceAppId].app;
|
|
1029
|
+
const processingGroupId = handlerApps[dataspaceAppId].processingGroupId;
|
|
1030
|
+
const payload = {
|
|
1031
|
+
activityLogEntryId,
|
|
1032
|
+
activity: activity,
|
|
1033
|
+
dataspaceAppId
|
|
1034
|
+
};
|
|
1035
|
+
// If there is no processing group we execute the task inline without creating a background task
|
|
1036
|
+
const isInlineTask = !Is.stringValue(processingGroupId);
|
|
1037
|
+
if (isInlineTask) {
|
|
1038
|
+
const handleActivity = handlerApp?.handleActivity?.bind(handlerApp);
|
|
1039
|
+
if (!Is.function(handleActivity)) {
|
|
1040
|
+
throw new GeneralError(DataspaceDataPlaneService.CLASS_NAME, "missingHandleActivity", {
|
|
1041
|
+
dataspaceAppId
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
let taskError;
|
|
1045
|
+
let taskResult;
|
|
1046
|
+
try {
|
|
1047
|
+
taskResult = await handleActivity(activity);
|
|
1048
|
+
}
|
|
1049
|
+
catch (error) {
|
|
1050
|
+
taskError = BaseError.fromError(error);
|
|
1051
|
+
}
|
|
1052
|
+
const now = Date.now();
|
|
1053
|
+
const taskEntry = {
|
|
1054
|
+
taskId: RandomHelper.generateUuidV7("compact"),
|
|
1055
|
+
dataspaceAppId,
|
|
1056
|
+
processingGroupId,
|
|
1057
|
+
result: taskResult,
|
|
1058
|
+
startDate: new Date(now).toISOString(),
|
|
1059
|
+
endDate: new Date(now).toISOString(),
|
|
1060
|
+
status: Is.empty(taskError) ? ActivityTaskStatus.Success : ActivityTaskStatus.Failed,
|
|
1061
|
+
error: taskError
|
|
1062
|
+
};
|
|
1063
|
+
taskEntries.push(taskEntry);
|
|
1064
|
+
await this.notifyTaskStatusChanged(payload.activityLogEntryId, payload.activity.id, payload.dataspaceAppId, taskEntry.taskId, taskEntry.status);
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
const processingGroups = handlerApp.processingGroups?.() ?? {};
|
|
1068
|
+
if (Is.empty(processingGroups[processingGroupId])) {
|
|
1069
|
+
throw new GeneralError(DataspaceDataPlaneService.CLASS_NAME, "invalidProcessingGroupId", {
|
|
1070
|
+
processingGroupId
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
const processingGroupOptions = processingGroups[processingGroupId];
|
|
1074
|
+
const taskType = `${dataspaceAppId}${processingGroupId ? `-${processingGroupId}` : ""}`;
|
|
1075
|
+
const taskId = await this._backgroundTaskComponent.create(taskType, payload, {
|
|
1076
|
+
retainFor: this._retainTasksFor,
|
|
1077
|
+
retryCount: processingGroupOptions?.retryCount ?? this._retryCount
|
|
1078
|
+
});
|
|
1079
|
+
if (!this._registeredTaskTypes.includes(taskType)) {
|
|
1080
|
+
this._registeredTaskTypes.push(taskType);
|
|
1081
|
+
await this._backgroundTaskComponent.registerHandler(taskType, "@twin.org/dataspace-app-runner", "appRunner", async (task) => {
|
|
1082
|
+
await this.finaliseBackgroundTask(task.id, task.status, task.payload);
|
|
1083
|
+
}, {
|
|
1084
|
+
maxWorkerCount: processingGroupOptions?.concurrentTasks,
|
|
1085
|
+
idleShutdownTimeout: processingGroupOptions?.idleShutdownTimeout,
|
|
1086
|
+
initialiseMethod: "appRunnerStart",
|
|
1087
|
+
shutdownMethod: "appRunnerEnd"
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
taskEntries.push({
|
|
1091
|
+
taskId,
|
|
1092
|
+
dataspaceAppId,
|
|
1093
|
+
processingGroupId,
|
|
1094
|
+
status: ActivityTaskStatus.Pending
|
|
1095
|
+
});
|
|
1096
|
+
await this._logging?.log({
|
|
1097
|
+
level: "info",
|
|
1098
|
+
source: DataspaceDataPlaneService.CLASS_NAME,
|
|
1099
|
+
message: "scheduledTask",
|
|
1100
|
+
data: {
|
|
1101
|
+
taskId,
|
|
1102
|
+
dataspaceAppId,
|
|
1103
|
+
isRetry
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
return isInlineTask;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Constructs the activity log entry with processing status and associated tasks.
|
|
1111
|
+
* @param activityLog The activity log details retrieved from storage.
|
|
1112
|
+
* @param activityTasks The activity tasks associated with the log entry, if any.
|
|
1113
|
+
* @returns The complete activity log entry with status and tasks.
|
|
1114
|
+
* @internal
|
|
1115
|
+
*/
|
|
1116
|
+
async constructLogEntry(activityLog, activityTasks) {
|
|
1117
|
+
let tasks;
|
|
1118
|
+
// For calculating the processing status. `Registering` if we cannot determine the activity tasks yet
|
|
1119
|
+
let status = ActivityProcessingStatus.Registering;
|
|
1120
|
+
// Now query the associated tasks
|
|
1121
|
+
// If activity tasks is undefined it is because the corresponding store has not been persisted yet
|
|
1122
|
+
if (!Is.undefined(activityTasks)) {
|
|
1123
|
+
tasks = [];
|
|
1124
|
+
const typeCount = {
|
|
1125
|
+
[TaskStatus.Pending]: 0,
|
|
1126
|
+
[TaskStatus.Processing]: 0,
|
|
1127
|
+
[TaskStatus.Success]: 0,
|
|
1128
|
+
[TaskStatus.Failed]: 0,
|
|
1129
|
+
[TaskStatus.Cancelled]: 0
|
|
1130
|
+
};
|
|
1131
|
+
for (const entity of activityTasks.associatedTasks) {
|
|
1132
|
+
let entry;
|
|
1133
|
+
if (!Is.stringValue(entity.processingGroupId)) {
|
|
1134
|
+
// If there is no process group, the task was processed inline so the task status is already available in the entity
|
|
1135
|
+
typeCount[entity.status]++;
|
|
1136
|
+
entry = entity;
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
const taskDetails = await this._backgroundTaskComponent.get(entity.taskId);
|
|
1140
|
+
if (!Is.empty(taskDetails)) {
|
|
1141
|
+
typeCount[taskDetails.status]++;
|
|
1142
|
+
switch (taskDetails.status) {
|
|
1143
|
+
case TaskStatus.Success:
|
|
1144
|
+
entry = {
|
|
1145
|
+
...entity,
|
|
1146
|
+
status: ActivityTaskStatus.Success,
|
|
1147
|
+
result: taskDetails.result,
|
|
1148
|
+
startDate: taskDetails?.dateCreated,
|
|
1149
|
+
endDate: taskDetails?.dateCompleted
|
|
1150
|
+
};
|
|
1151
|
+
break;
|
|
1152
|
+
case TaskStatus.Pending:
|
|
1153
|
+
entry = { ...entity, status: ActivityTaskStatus.Pending };
|
|
1154
|
+
break;
|
|
1155
|
+
case TaskStatus.Processing:
|
|
1156
|
+
entry = {
|
|
1157
|
+
...entity,
|
|
1158
|
+
status: ActivityTaskStatus.Processing,
|
|
1159
|
+
startDate: taskDetails.dateCreated
|
|
1160
|
+
};
|
|
1161
|
+
break;
|
|
1162
|
+
case TaskStatus.Failed:
|
|
1163
|
+
entry = {
|
|
1164
|
+
...entity,
|
|
1165
|
+
status: ActivityTaskStatus.Failed,
|
|
1166
|
+
error: taskDetails.error
|
|
1167
|
+
};
|
|
1168
|
+
break;
|
|
1169
|
+
case TaskStatus.Cancelled:
|
|
1170
|
+
// Nothing to do for cancelled tasks
|
|
1171
|
+
break;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
if (!Is.empty(entry)) {
|
|
1176
|
+
tasks.push(entry);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (typeCount[TaskStatus.Failed] > 0) {
|
|
1180
|
+
status = ActivityProcessingStatus.Error;
|
|
1181
|
+
}
|
|
1182
|
+
else if (typeCount[TaskStatus.Processing] > 0) {
|
|
1183
|
+
status = ActivityProcessingStatus.Running;
|
|
1184
|
+
}
|
|
1185
|
+
else if (typeCount[TaskStatus.Pending] > 0) {
|
|
1186
|
+
status = ActivityProcessingStatus.Pending;
|
|
1187
|
+
}
|
|
1188
|
+
else {
|
|
1189
|
+
status = ActivityProcessingStatus.Completed;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return { ...activityLog, status, tasks };
|
|
1193
|
+
}
|
|
1071
1194
|
}
|
|
1072
1195
|
//# sourceMappingURL=dataspaceDataPlaneService.js.map
|