@twin.org/dataspace-data-plane-service 0.0.3-next.25 → 0.0.3-next.27
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 +19 -14
- package/dist/es/dataspaceDataPlaneRoutes.js.map +1 -1
- package/dist/es/dataspaceDataPlaneService.js +306 -182
- 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/es/models/IDataspaceDataPlaneServiceConstructorOptions.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/dist/types/models/IDataspaceDataPlaneServiceConstructorOptions.d.ts +5 -0
- package/docs/changelog.md +137 -103
- package/docs/open-api/spec.json +78 -110
- 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/docs/reference/interfaces/IDataspaceDataPlaneServiceConstructorOptions.md +14 -0
- package/locales/en.json +3 -2
- package/package.json +5 -5
|
@@ -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
|
|
@@ -121,6 +131,11 @@ export class DataspaceDataPlaneService {
|
|
|
121
131
|
* @internal
|
|
122
132
|
*/
|
|
123
133
|
_transferProcessStorage;
|
|
134
|
+
/**
|
|
135
|
+
* Entity storage for tenant-supplied Dataspace App Dataset entities.
|
|
136
|
+
* @internal
|
|
137
|
+
*/
|
|
138
|
+
_dataspaceAppDatasetStorage;
|
|
124
139
|
/**
|
|
125
140
|
* Create a new instance of DataspaceDataPlane.
|
|
126
141
|
* @param options The options for the data plane.
|
|
@@ -138,17 +153,20 @@ export class DataspaceDataPlaneService {
|
|
|
138
153
|
// Entity storage for Transfer Process state lookup
|
|
139
154
|
// Used to read transfer state from shared storage (written by Control Plane)
|
|
140
155
|
this._transferProcessStorage = EntityStorageConnectorFactory.get(options?.transferProcessEntityStorageType ?? "transfer-process");
|
|
156
|
+
this._dataspaceAppDatasetStorage = EntityStorageConnectorFactory.get(options?.dataspaceAppDatasetEntityStorageType ?? "dataspace-app-dataset");
|
|
141
157
|
JsonLdDataTypes.registerTypes();
|
|
142
158
|
DataspaceDataTypes.registerTypes();
|
|
143
159
|
SchemaOrgDataTypes.registerRedirects();
|
|
144
160
|
DataspaceProtocolDataTypes.registerRedirects();
|
|
145
161
|
DataspaceProtocolDataTypes.registerTypes();
|
|
146
162
|
this._activityLogStatusCallbacks = {};
|
|
163
|
+
this._registeredTaskTypes = [];
|
|
147
164
|
this._partitionContextIds = options?.partitionContextIds;
|
|
148
165
|
this._retainTasksFor =
|
|
149
166
|
DataspaceDataPlaneService._DEFAULT_RETAIN_INTERVAL * DataspaceDataPlaneService._MS_PER_MINUTE;
|
|
150
167
|
this._retainActivityLogsFor =
|
|
151
168
|
DataspaceDataPlaneService._DEFAULT_RETAIN_INTERVAL * DataspaceDataPlaneService._MS_PER_MINUTE;
|
|
169
|
+
this._retryCount = options?.config?.retryCount;
|
|
152
170
|
this._activityLogCleanUpInterval = DataspaceDataPlaneService._DEFAULT_CLEANUP_INTERVAL;
|
|
153
171
|
this._cleanUpProcessOngoing = false;
|
|
154
172
|
const validationErrors = [];
|
|
@@ -225,7 +243,7 @@ export class DataspaceDataPlaneService {
|
|
|
225
243
|
/**
|
|
226
244
|
* Notify an Activity.
|
|
227
245
|
* @param activity The Activity notified.
|
|
228
|
-
* @returns The
|
|
246
|
+
* @returns The activity's id or entry.
|
|
229
247
|
*/
|
|
230
248
|
async notifyActivity(activity) {
|
|
231
249
|
Guards.object(DataspaceDataPlaneService.CLASS_NAME, "activity", activity);
|
|
@@ -255,10 +273,11 @@ export class DataspaceDataPlaneService {
|
|
|
255
273
|
const activityLogId = Converter.bytesToHex(Blake2b.sum256(canonicalBytes));
|
|
256
274
|
const activityLogEntryId = `urn:x-activity-log:${activityLogId}`;
|
|
257
275
|
// Check if entry already exists
|
|
258
|
-
|
|
276
|
+
let logEntry = await this._entityStorageActivityLogs.get(activityLogEntryId);
|
|
259
277
|
let existingSuccessfulApps = [];
|
|
260
278
|
let isRetry = false;
|
|
261
|
-
|
|
279
|
+
const now = Date.now();
|
|
280
|
+
if (!Is.undefined(logEntry)) {
|
|
262
281
|
// Check if there are failed tasks that can be retried
|
|
263
282
|
const existingEntry = await this.getActivityLogEntry(activityLogEntryId);
|
|
264
283
|
// If all tasks completed successfully, this is a duplicate
|
|
@@ -276,69 +295,46 @@ export class DataspaceDataPlaneService {
|
|
|
276
295
|
isRetry = true;
|
|
277
296
|
}
|
|
278
297
|
else {
|
|
279
|
-
|
|
298
|
+
logEntry = {
|
|
280
299
|
id: activityLogEntryId,
|
|
281
|
-
activityId:
|
|
300
|
+
activityId: activity.id,
|
|
282
301
|
generator: this.calculateActivityGeneratorIdentity(activity),
|
|
283
|
-
dateCreated: new Date().toISOString(),
|
|
284
|
-
dateModified: new Date().toISOString()
|
|
302
|
+
dateCreated: new Date(now).toISOString(),
|
|
303
|
+
dateModified: new Date(now).toISOString()
|
|
285
304
|
};
|
|
286
305
|
await this._entityStorageActivityLogs.set(logEntry);
|
|
287
306
|
}
|
|
288
307
|
const activityQuerySet = await this.calculateActivityQuerySet(activity);
|
|
289
|
-
const
|
|
290
|
-
const
|
|
308
|
+
const taskEntries = [];
|
|
309
|
+
const handlerApps = {};
|
|
291
310
|
for (const query of activityQuerySet) {
|
|
292
|
-
const
|
|
293
|
-
for (const appId
|
|
294
|
-
|
|
295
|
-
|
|
311
|
+
const apps = this.getAppForActivityQuery(query);
|
|
312
|
+
for (const appId in apps) {
|
|
313
|
+
// Only process apps that haven't already completed successfully
|
|
314
|
+
if (!handlerApps[appId] && !existingSuccessfulApps.includes(appId)) {
|
|
315
|
+
handlerApps[appId] = apps[appId];
|
|
296
316
|
}
|
|
297
317
|
}
|
|
298
318
|
}
|
|
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
|
-
});
|
|
319
|
+
let inlineCount = 0;
|
|
320
|
+
for (const handlerAppId in handlerApps) {
|
|
321
|
+
if (await this.processTask(activityLogEntryId, activity, handlerApps, handlerAppId, taskEntries, isRetry)) {
|
|
322
|
+
inlineCount++;
|
|
331
323
|
}
|
|
332
324
|
}
|
|
333
325
|
const existingActivityTasks = isRetry
|
|
334
326
|
? await this._entityStorageActivityTasks.get(activityLogEntryId)
|
|
335
327
|
: undefined;
|
|
336
328
|
const existingTasksToKeep = existingActivityTasks?.associatedTasks.filter(t => existingSuccessfulApps.includes(t.dataspaceAppId)) ?? [];
|
|
337
|
-
|
|
329
|
+
const activityTask = {
|
|
338
330
|
activityLogEntryId,
|
|
339
|
-
associatedTasks: [...existingTasksToKeep, ...
|
|
340
|
-
}
|
|
341
|
-
|
|
331
|
+
associatedTasks: [...existingTasksToKeep, ...taskEntries]
|
|
332
|
+
};
|
|
333
|
+
await this._entityStorageActivityTasks.set(activityTask);
|
|
334
|
+
if (inlineCount === taskEntries.length) {
|
|
335
|
+
return this.finaliseActivityLogEntry(activityLogEntryId);
|
|
336
|
+
}
|
|
337
|
+
return activityTask.activityLogEntryId;
|
|
342
338
|
}
|
|
343
339
|
/**
|
|
344
340
|
* Subscribes to the activity log.
|
|
@@ -370,68 +366,12 @@ export class DataspaceDataPlaneService {
|
|
|
370
366
|
*/
|
|
371
367
|
async getActivityLogEntry(logEntryId) {
|
|
372
368
|
Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "logEntryId", logEntryId);
|
|
373
|
-
const
|
|
374
|
-
if (Is.undefined(
|
|
369
|
+
const activityLog = await this._entityStorageActivityLogs.get(logEntryId);
|
|
370
|
+
if (Is.undefined(activityLog)) {
|
|
375
371
|
throw new NotFoundError(DataspaceDataPlaneService.CLASS_NAME, "activityLogEntryNotFound", logEntryId);
|
|
376
372
|
}
|
|
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
373
|
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 };
|
|
374
|
+
return this.constructLogEntry(activityLog, activityTasks);
|
|
435
375
|
}
|
|
436
376
|
/**
|
|
437
377
|
* Get Data Asset entities. Allows to retrieve entities by their type or id.
|
|
@@ -471,9 +411,7 @@ export class DataspaceDataPlaneService {
|
|
|
471
411
|
}
|
|
472
412
|
const datasetId = serviceDataset["@id"];
|
|
473
413
|
Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "datasetId", datasetId);
|
|
474
|
-
const
|
|
475
|
-
// getAppForDataAssetQuery already validates app exists
|
|
476
|
-
const app = DataspaceAppFactory.get(appId);
|
|
414
|
+
const app = await this.getAppForDataAssetQuery({ datasetId });
|
|
477
415
|
const handleDataRequest = app.handleDataRequest?.bind(app);
|
|
478
416
|
Guards.function(DataspaceDataPlaneService.CLASS_NAME, "handleDataRequest", handleDataRequest);
|
|
479
417
|
const dataRequest = {
|
|
@@ -537,8 +475,7 @@ export class DataspaceDataPlaneService {
|
|
|
537
475
|
const serviceDataset = await this.getDatasetFromApps(resolvedDatasetId);
|
|
538
476
|
const datasetId = serviceDataset["@id"];
|
|
539
477
|
Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "datasetId", datasetId);
|
|
540
|
-
const
|
|
541
|
-
const app = DataspaceAppFactory.get(appId);
|
|
478
|
+
const app = await this.getAppForDataAssetQuery({ datasetId });
|
|
542
479
|
if (!app.supportedQueryTypes().includes(query.type)) {
|
|
543
480
|
throw new UnprocessableError(DataspaceDataPlaneService.CLASS_NAME, "queryTypeNotSupported", {
|
|
544
481
|
queryType: query.type
|
|
@@ -643,55 +580,65 @@ export class DataspaceDataPlaneService {
|
|
|
643
580
|
}
|
|
644
581
|
/**
|
|
645
582
|
* Process activity task finalization.
|
|
646
|
-
* @param
|
|
583
|
+
* @param taskId The Id of the Activity Log Entry.
|
|
584
|
+
* @param status The final status of the task.
|
|
585
|
+
* @param payload The execution payload of the task, required to correlate to the Activity Log Entry and update the processing status.
|
|
647
586
|
* @internal
|
|
648
587
|
*/
|
|
649
|
-
async
|
|
650
|
-
const payload = task.payload;
|
|
588
|
+
async finaliseBackgroundTask(taskId, status, payload) {
|
|
651
589
|
if (Is.empty(payload)) {
|
|
652
590
|
return;
|
|
653
591
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
await this.
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
592
|
+
if (status === TaskStatus.Success || status === TaskStatus.Failed) {
|
|
593
|
+
await this.notifyTaskStatusChanged(payload.activityLogEntryId, payload.activity.id, payload.dataspaceAppId, taskId, status);
|
|
594
|
+
await this.finaliseActivityLogEntry(payload.activityLogEntryId);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Notify registered callbacks about a task status change.
|
|
599
|
+
* @param activityLogEntryId The Id of the Activity Log Entry.
|
|
600
|
+
* @param activityId The Id of the Activity.
|
|
601
|
+
* @param dataspaceAppId The Id of the Dataspace App associated with the task.
|
|
602
|
+
* @param taskId The Id of the task.
|
|
603
|
+
* @param taskStatus The new status of the task.
|
|
604
|
+
* @internal
|
|
605
|
+
*/
|
|
606
|
+
async notifyTaskStatusChanged(activityLogEntryId, activityId, dataspaceAppId, taskId, taskStatus) {
|
|
607
|
+
for (const callback of Object.values(this._activityLogStatusCallbacks)) {
|
|
608
|
+
await callback({
|
|
609
|
+
activityLogEntryId,
|
|
610
|
+
activityId,
|
|
611
|
+
taskProcessingStatus: {
|
|
612
|
+
dataspaceAppId,
|
|
613
|
+
taskId,
|
|
614
|
+
taskStatus
|
|
662
615
|
}
|
|
663
616
|
});
|
|
664
617
|
}
|
|
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
|
-
}
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* 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.
|
|
621
|
+
* @param activityLogEntryId The Id of the Activity Log Entry to finalize.
|
|
622
|
+
* @returns The Activity Log Entry with updated retention details if applicable.
|
|
623
|
+
* @internal
|
|
624
|
+
*/
|
|
625
|
+
async finaliseActivityLogEntry(activityLogEntryId) {
|
|
626
|
+
const entry = await this.getActivityLogEntry(activityLogEntryId);
|
|
627
|
+
if (this._retainActivityLogsFor !== -1 &&
|
|
628
|
+
(entry.status === ActivityProcessingStatus.Completed ||
|
|
629
|
+
entry.status === ActivityProcessingStatus.Error)) {
|
|
630
|
+
const retainUntil = Date.now() + this._retainActivityLogsFor;
|
|
631
|
+
const updatedEntry = {
|
|
632
|
+
id: entry.id,
|
|
633
|
+
activityId: entry.activityId,
|
|
634
|
+
generator: entry.generator,
|
|
635
|
+
dateCreated: entry.dateCreated,
|
|
636
|
+
dateModified: entry.dateModified,
|
|
637
|
+
retainUntil
|
|
638
|
+
};
|
|
639
|
+
await this._entityStorageActivityLogs.set(updatedEntry);
|
|
694
640
|
}
|
|
641
|
+
return entry;
|
|
695
642
|
}
|
|
696
643
|
/**
|
|
697
644
|
* Cleans up the activity log by deleting those entries that no longer shall be retained.
|
|
@@ -850,7 +797,7 @@ export class DataspaceDataPlaneService {
|
|
|
850
797
|
* @internal
|
|
851
798
|
*/
|
|
852
799
|
getAppForActivityQuery(activityQuery) {
|
|
853
|
-
const matchingElements =
|
|
800
|
+
const matchingElements = {};
|
|
854
801
|
const appNames = DataspaceAppFactory.names();
|
|
855
802
|
for (const appId of appNames) {
|
|
856
803
|
const app = DataspaceAppFactory.get(appId);
|
|
@@ -860,17 +807,18 @@ export class DataspaceDataPlaneService {
|
|
|
860
807
|
(Is.undefined(appQuery.activityType) ||
|
|
861
808
|
appQuery.activityType === activityQuery.activityType) &&
|
|
862
809
|
(Is.undefined(appQuery.targetType) || appQuery.targetType === activityQuery.targetType)) {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
}
|
|
810
|
+
matchingElements[appId] = {
|
|
811
|
+
app,
|
|
812
|
+
processingGroupId: appQuery.processingGroupId
|
|
813
|
+
};
|
|
867
814
|
}
|
|
868
815
|
}
|
|
869
816
|
}
|
|
870
817
|
return matchingElements;
|
|
871
818
|
}
|
|
872
819
|
/**
|
|
873
|
-
* Get a dataset
|
|
820
|
+
* Get a dataset by its ID. Resolves via the tenant-supplied dataspace app
|
|
821
|
+
* datasets stored by the Control Plane.
|
|
874
822
|
* @param datasetId The dataset identifier (@id)
|
|
875
823
|
* @returns The dataset
|
|
876
824
|
* @throws NotFoundError if no app handles this dataset
|
|
@@ -878,14 +826,12 @@ export class DataspaceDataPlaneService {
|
|
|
878
826
|
*/
|
|
879
827
|
async getDatasetFromApps(datasetId) {
|
|
880
828
|
Guards.stringValue(DataspaceDataPlaneService.CLASS_NAME, "datasetId", datasetId);
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
return dataset;
|
|
888
|
-
}
|
|
829
|
+
const fromAppDataset = await this._dataspaceAppDatasetStorage.get(datasetId);
|
|
830
|
+
if (!Is.empty(fromAppDataset)) {
|
|
831
|
+
return {
|
|
832
|
+
...fromAppDataset.dataset,
|
|
833
|
+
"@id": datasetId
|
|
834
|
+
};
|
|
889
835
|
}
|
|
890
836
|
throw new NotFoundError(DataspaceDataPlaneService.CLASS_NAME, "noAppRegistered", datasetId, {
|
|
891
837
|
datasetId
|
|
@@ -899,18 +845,16 @@ export class DataspaceDataPlaneService {
|
|
|
899
845
|
*/
|
|
900
846
|
async getAppForDataAssetQuery(dataAssetQuery) {
|
|
901
847
|
const matchingElements = [];
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
}
|
|
910
|
-
}
|
|
848
|
+
const matchingIds = [];
|
|
849
|
+
// Storage primary key is the dataset's @id, so a single get() resolves it.
|
|
850
|
+
const fromAppDataset = await this._dataspaceAppDatasetStorage.get(dataAssetQuery.datasetId);
|
|
851
|
+
if (!Is.empty(fromAppDataset)) {
|
|
852
|
+
const app = DataspaceAppFactory.get(fromAppDataset.appId);
|
|
853
|
+
matchingElements.push(app);
|
|
854
|
+
matchingIds.push(fromAppDataset.appId);
|
|
911
855
|
}
|
|
912
856
|
if (matchingElements.length > 1) {
|
|
913
|
-
const error = new ConflictError(DataspaceDataPlaneService.CLASS_NAME, "tooManyAppsRegistered", dataAssetQuery.datasetId,
|
|
857
|
+
const error = new ConflictError(DataspaceDataPlaneService.CLASS_NAME, "tooManyAppsRegistered", dataAssetQuery.datasetId, matchingIds, {
|
|
914
858
|
datasetId: dataAssetQuery.datasetId
|
|
915
859
|
});
|
|
916
860
|
await this._logging?.log({
|
|
@@ -949,11 +893,15 @@ export class DataspaceDataPlaneService {
|
|
|
949
893
|
* @internal
|
|
950
894
|
*/
|
|
951
895
|
async prepareForRetry(activityLogEntryId, existingEntry) {
|
|
952
|
-
const appsToRetry = existingEntry.
|
|
896
|
+
const appsToRetry = existingEntry.tasks
|
|
897
|
+
?.filter(t => t.status === ActivityTaskStatus.Failed)
|
|
898
|
+
.map(t => t.dataspaceAppId) ?? [];
|
|
953
899
|
if (!Is.arrayValue(appsToRetry)) {
|
|
954
900
|
throw new NotFoundError(DataspaceDataPlaneService.CLASS_NAME, "noFailedTasksToRetry", activityLogEntryId);
|
|
955
901
|
}
|
|
956
|
-
const successfulApps = existingEntry.
|
|
902
|
+
const successfulApps = existingEntry.tasks
|
|
903
|
+
?.filter(t => t.status === ActivityTaskStatus.Success)
|
|
904
|
+
.map(t => t.dataspaceAppId) ?? [];
|
|
957
905
|
await this._logging?.log({
|
|
958
906
|
level: "debug",
|
|
959
907
|
source: DataspaceDataPlaneService.CLASS_NAME,
|
|
@@ -971,8 +919,6 @@ export class DataspaceDataPlaneService {
|
|
|
971
919
|
if (this._retainActivityLogsFor !== -1) {
|
|
972
920
|
logEntry.retainUntil = Date.now() + this._retainActivityLogsFor;
|
|
973
921
|
}
|
|
974
|
-
// Monitoring purposes
|
|
975
|
-
logEntry.retryCount = (logEntry.retryCount ?? 0) + 1;
|
|
976
922
|
await this._entityStorageActivityLogs.set(logEntry);
|
|
977
923
|
}
|
|
978
924
|
return successfulApps;
|
|
@@ -1068,5 +1014,183 @@ export class DataspaceDataPlaneService {
|
|
|
1068
1014
|
});
|
|
1069
1015
|
}
|
|
1070
1016
|
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Processes a task for an activity, by creating a background task and registering the handler.
|
|
1019
|
+
* @param activityLogEntryId The ID of the activity log entry.
|
|
1020
|
+
* @param activity The activity to be processed.
|
|
1021
|
+
* @param handlerApps The handler applications for the activity.
|
|
1022
|
+
* @param dataspaceAppId The ID of the handler application.
|
|
1023
|
+
* @param taskEntries The list of activity log entries.
|
|
1024
|
+
* @param isRetry Indicates if this is a retry of a previous task.
|
|
1025
|
+
* @returns True if the task was processed inline.
|
|
1026
|
+
* @internal
|
|
1027
|
+
*/
|
|
1028
|
+
async processTask(activityLogEntryId, activity, handlerApps, dataspaceAppId, taskEntries, isRetry) {
|
|
1029
|
+
const handlerApp = handlerApps[dataspaceAppId].app;
|
|
1030
|
+
const processingGroupId = handlerApps[dataspaceAppId].processingGroupId;
|
|
1031
|
+
const payload = {
|
|
1032
|
+
activityLogEntryId,
|
|
1033
|
+
activity: activity,
|
|
1034
|
+
dataspaceAppId
|
|
1035
|
+
};
|
|
1036
|
+
// If there is no processing group we execute the task inline without creating a background task
|
|
1037
|
+
const isInlineTask = !Is.stringValue(processingGroupId);
|
|
1038
|
+
if (isInlineTask) {
|
|
1039
|
+
const handleActivity = handlerApp?.handleActivity?.bind(handlerApp);
|
|
1040
|
+
if (!Is.function(handleActivity)) {
|
|
1041
|
+
throw new GeneralError(DataspaceDataPlaneService.CLASS_NAME, "missingHandleActivity", {
|
|
1042
|
+
dataspaceAppId
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
let taskError;
|
|
1046
|
+
let taskResult;
|
|
1047
|
+
try {
|
|
1048
|
+
taskResult = await handleActivity(activity);
|
|
1049
|
+
}
|
|
1050
|
+
catch (error) {
|
|
1051
|
+
taskError = BaseError.fromError(error);
|
|
1052
|
+
}
|
|
1053
|
+
const now = Date.now();
|
|
1054
|
+
const taskEntry = {
|
|
1055
|
+
taskId: RandomHelper.generateUuidV7("compact"),
|
|
1056
|
+
dataspaceAppId,
|
|
1057
|
+
processingGroupId,
|
|
1058
|
+
result: taskResult,
|
|
1059
|
+
startDate: new Date(now).toISOString(),
|
|
1060
|
+
endDate: new Date(now).toISOString(),
|
|
1061
|
+
status: Is.empty(taskError) ? ActivityTaskStatus.Success : ActivityTaskStatus.Failed,
|
|
1062
|
+
error: taskError
|
|
1063
|
+
};
|
|
1064
|
+
taskEntries.push(taskEntry);
|
|
1065
|
+
await this.notifyTaskStatusChanged(payload.activityLogEntryId, payload.activity.id, payload.dataspaceAppId, taskEntry.taskId, taskEntry.status);
|
|
1066
|
+
}
|
|
1067
|
+
else {
|
|
1068
|
+
const processingGroups = handlerApp.processingGroups?.() ?? {};
|
|
1069
|
+
if (Is.empty(processingGroups[processingGroupId])) {
|
|
1070
|
+
throw new GeneralError(DataspaceDataPlaneService.CLASS_NAME, "invalidProcessingGroupId", {
|
|
1071
|
+
processingGroupId
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
const processingGroupOptions = processingGroups[processingGroupId];
|
|
1075
|
+
const taskType = `${dataspaceAppId}${processingGroupId ? `-${processingGroupId}` : ""}`;
|
|
1076
|
+
const taskId = await this._backgroundTaskComponent.create(taskType, payload, {
|
|
1077
|
+
retainFor: this._retainTasksFor,
|
|
1078
|
+
retryCount: processingGroupOptions?.retryCount ?? this._retryCount
|
|
1079
|
+
});
|
|
1080
|
+
if (!this._registeredTaskTypes.includes(taskType)) {
|
|
1081
|
+
this._registeredTaskTypes.push(taskType);
|
|
1082
|
+
await this._backgroundTaskComponent.registerHandler(taskType, "@twin.org/dataspace-app-runner", "appRunner", async (task) => {
|
|
1083
|
+
await this.finaliseBackgroundTask(task.id, task.status, task.payload);
|
|
1084
|
+
}, {
|
|
1085
|
+
maxWorkerCount: processingGroupOptions?.concurrentTasks,
|
|
1086
|
+
idleShutdownTimeout: processingGroupOptions?.idleShutdownTimeout,
|
|
1087
|
+
initialiseMethod: "appRunnerStart",
|
|
1088
|
+
shutdownMethod: "appRunnerEnd"
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
taskEntries.push({
|
|
1092
|
+
taskId,
|
|
1093
|
+
dataspaceAppId,
|
|
1094
|
+
processingGroupId,
|
|
1095
|
+
status: ActivityTaskStatus.Pending
|
|
1096
|
+
});
|
|
1097
|
+
await this._logging?.log({
|
|
1098
|
+
level: "info",
|
|
1099
|
+
source: DataspaceDataPlaneService.CLASS_NAME,
|
|
1100
|
+
message: "scheduledTask",
|
|
1101
|
+
data: {
|
|
1102
|
+
taskId,
|
|
1103
|
+
dataspaceAppId,
|
|
1104
|
+
isRetry
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
return isInlineTask;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Constructs the activity log entry with processing status and associated tasks.
|
|
1112
|
+
* @param activityLog The activity log details retrieved from storage.
|
|
1113
|
+
* @param activityTasks The activity tasks associated with the log entry, if any.
|
|
1114
|
+
* @returns The complete activity log entry with status and tasks.
|
|
1115
|
+
* @internal
|
|
1116
|
+
*/
|
|
1117
|
+
async constructLogEntry(activityLog, activityTasks) {
|
|
1118
|
+
let tasks;
|
|
1119
|
+
// For calculating the processing status. `Registering` if we cannot determine the activity tasks yet
|
|
1120
|
+
let status = ActivityProcessingStatus.Registering;
|
|
1121
|
+
// Now query the associated tasks
|
|
1122
|
+
// If activity tasks is undefined it is because the corresponding store has not been persisted yet
|
|
1123
|
+
if (!Is.undefined(activityTasks)) {
|
|
1124
|
+
tasks = [];
|
|
1125
|
+
const typeCount = {
|
|
1126
|
+
[TaskStatus.Pending]: 0,
|
|
1127
|
+
[TaskStatus.Processing]: 0,
|
|
1128
|
+
[TaskStatus.Success]: 0,
|
|
1129
|
+
[TaskStatus.Failed]: 0,
|
|
1130
|
+
[TaskStatus.Cancelled]: 0
|
|
1131
|
+
};
|
|
1132
|
+
for (const entity of activityTasks.associatedTasks) {
|
|
1133
|
+
let entry;
|
|
1134
|
+
if (!Is.stringValue(entity.processingGroupId)) {
|
|
1135
|
+
// If there is no process group, the task was processed inline so the task status is already available in the entity
|
|
1136
|
+
typeCount[entity.status]++;
|
|
1137
|
+
entry = entity;
|
|
1138
|
+
}
|
|
1139
|
+
else {
|
|
1140
|
+
const taskDetails = await this._backgroundTaskComponent.get(entity.taskId);
|
|
1141
|
+
if (!Is.empty(taskDetails)) {
|
|
1142
|
+
typeCount[taskDetails.status]++;
|
|
1143
|
+
switch (taskDetails.status) {
|
|
1144
|
+
case TaskStatus.Success:
|
|
1145
|
+
entry = {
|
|
1146
|
+
...entity,
|
|
1147
|
+
status: ActivityTaskStatus.Success,
|
|
1148
|
+
result: taskDetails.result,
|
|
1149
|
+
startDate: taskDetails?.dateCreated,
|
|
1150
|
+
endDate: taskDetails?.dateCompleted
|
|
1151
|
+
};
|
|
1152
|
+
break;
|
|
1153
|
+
case TaskStatus.Pending:
|
|
1154
|
+
entry = { ...entity, status: ActivityTaskStatus.Pending };
|
|
1155
|
+
break;
|
|
1156
|
+
case TaskStatus.Processing:
|
|
1157
|
+
entry = {
|
|
1158
|
+
...entity,
|
|
1159
|
+
status: ActivityTaskStatus.Processing,
|
|
1160
|
+
startDate: taskDetails.dateCreated
|
|
1161
|
+
};
|
|
1162
|
+
break;
|
|
1163
|
+
case TaskStatus.Failed:
|
|
1164
|
+
entry = {
|
|
1165
|
+
...entity,
|
|
1166
|
+
status: ActivityTaskStatus.Failed,
|
|
1167
|
+
error: taskDetails.error
|
|
1168
|
+
};
|
|
1169
|
+
break;
|
|
1170
|
+
case TaskStatus.Cancelled:
|
|
1171
|
+
// Nothing to do for cancelled tasks
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (!Is.empty(entry)) {
|
|
1177
|
+
tasks.push(entry);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (typeCount[TaskStatus.Failed] > 0) {
|
|
1181
|
+
status = ActivityProcessingStatus.Error;
|
|
1182
|
+
}
|
|
1183
|
+
else if (typeCount[TaskStatus.Processing] > 0) {
|
|
1184
|
+
status = ActivityProcessingStatus.Running;
|
|
1185
|
+
}
|
|
1186
|
+
else if (typeCount[TaskStatus.Pending] > 0) {
|
|
1187
|
+
status = ActivityProcessingStatus.Pending;
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
status = ActivityProcessingStatus.Completed;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return { ...activityLog, status, tasks };
|
|
1194
|
+
}
|
|
1071
1195
|
}
|
|
1072
1196
|
//# sourceMappingURL=dataspaceDataPlaneService.js.map
|