@cap-js-community/event-queue 1.3.6 → 1.4.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/cds-plugin.js +1 -1
- package/package.json +6 -5
- package/src/EventQueueProcessorBase.js +2 -102
- package/src/config.js +29 -24
- package/src/dbHandler.js +11 -8
- package/src/initialize.js +8 -67
- package/src/processEventQueue.js +0 -6
- package/src/redis/redisPub.js +90 -0
- package/src/redis/redisSub.js +92 -0
- package/src/runner/openEvents.js +52 -0
- package/src/{runner.js → runner/runner.js} +129 -117
- package/src/runner/runnerHelper.js +37 -0
- package/src/shared/cdsHelper.js +10 -28
- package/src/shared/common.js +52 -1
- package/src/shared/distributedLock.js +3 -3
- package/src/shared/eventScheduler.js +2 -2
- package/src/shared/redis.js +15 -14
- package/src/redisPubSub.js +0 -170
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const cds = require("@sap/cds");
|
|
4
|
+
|
|
5
|
+
const eventConfig = require("../config");
|
|
6
|
+
const { EventProcessingStatus } = require("../constants");
|
|
7
|
+
|
|
8
|
+
const getOpenQueueEntries = async (tx) => {
|
|
9
|
+
const startTime = new Date();
|
|
10
|
+
const refDateStartAfter = new Date(startTime.getTime() + eventConfig.runInterval * 1.2);
|
|
11
|
+
const entries = await tx.run(
|
|
12
|
+
SELECT.from(eventConfig.tableNameEventQueue)
|
|
13
|
+
.where(
|
|
14
|
+
"( startAfter IS NULL OR startAfter <=",
|
|
15
|
+
refDateStartAfter.toISOString(),
|
|
16
|
+
" ) AND ( status =",
|
|
17
|
+
EventProcessingStatus.Open,
|
|
18
|
+
"OR ( status =",
|
|
19
|
+
EventProcessingStatus.Error,
|
|
20
|
+
") OR ( status =",
|
|
21
|
+
EventProcessingStatus.InProgress,
|
|
22
|
+
"AND lastAttemptTimestamp <=",
|
|
23
|
+
new Date(startTime.getTime() - eventConfig.globalTxTimeout).toISOString(),
|
|
24
|
+
") )"
|
|
25
|
+
)
|
|
26
|
+
.columns("type", "subType")
|
|
27
|
+
.groupBy("type", "subType")
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const result = [];
|
|
31
|
+
for (const { type, subType } of entries) {
|
|
32
|
+
if (type.startsWith("CAP_OUTBOX")) {
|
|
33
|
+
if (cds.requires[subType]) {
|
|
34
|
+
result.push({ type, subType });
|
|
35
|
+
} else {
|
|
36
|
+
const service = await cds.connect.to(subType).catch(() => {});
|
|
37
|
+
if (service) {
|
|
38
|
+
result.push({ type, subType });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
if (eventConfig.getEventConfig(type, subType)) {
|
|
43
|
+
result.push({ type, subType });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
getOpenQueueEntries,
|
|
52
|
+
};
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const { randomUUID } = require("crypto");
|
|
4
|
-
const { AsyncResource } = require("async_hooks");
|
|
5
4
|
|
|
6
5
|
const cds = require("@sap/cds");
|
|
7
6
|
|
|
8
|
-
const eventQueueConfig = require("
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const {
|
|
7
|
+
const eventQueueConfig = require("../config");
|
|
8
|
+
const WorkerQueue = require("../shared/WorkerQueue");
|
|
9
|
+
const cdsHelper = require("../shared/cdsHelper");
|
|
10
|
+
const distributedLock = require("../shared/distributedLock");
|
|
11
|
+
const SetIntervalDriftSafe = require("../shared/SetIntervalDriftSafe");
|
|
12
|
+
const periodicEvents = require("../periodicEvents");
|
|
13
|
+
const common = require("../shared/common");
|
|
14
|
+
const config = require("../config");
|
|
15
|
+
const redisPub = require("../redis/redisPub");
|
|
16
|
+
const openEvents = require("./openEvents");
|
|
17
|
+
const { runEventCombinationForTenant } = require("./runnerHelper");
|
|
19
18
|
|
|
20
19
|
const COMPONENT_NAME = "/eventQueue/runner";
|
|
21
20
|
const EVENT_QUEUE_RUN_ID = "EVENT_QUEUE_RUN_ID";
|
|
22
21
|
const EVENT_QUEUE_RUN_TS = "EVENT_QUEUE_RUN_TS";
|
|
23
|
-
const
|
|
22
|
+
const EVENT_QUEUE_RUN_REDIS_CHECK = "EVENT_QUEUE_RUN_REDIS_CHECK";
|
|
23
|
+
const EVENT_QUEUE_UPDATE_PERIODIC_EVENTS = "EVENT_QUEUE_UPDATE_PERIODIC_EVENTS";
|
|
24
24
|
const OFFSET_FIRST_RUN = 10 * 1000;
|
|
25
25
|
|
|
26
26
|
let tenantIdHash;
|
|
27
27
|
let singleRunDone;
|
|
28
28
|
|
|
29
|
-
const singleTenant = () => _scheduleFunction(
|
|
29
|
+
const singleTenant = () => _scheduleFunction(_checkPeriodicEventsSingleTenantOneTime, _singleTenantDb);
|
|
30
30
|
|
|
31
31
|
const multiTenancyDb = () => _scheduleFunction(async () => {}, _multiTenancyDb);
|
|
32
32
|
|
|
@@ -69,26 +69,17 @@ const _scheduleFunction = async (singleRunFn, periodicFn) => {
|
|
|
69
69
|
const _multiTenancyRedis = async () => {
|
|
70
70
|
const logger = cds.log(COMPONENT_NAME);
|
|
71
71
|
try {
|
|
72
|
-
const emptyContext = new cds.EventContext({});
|
|
73
72
|
logger.info("executing event queue run for multi instance and tenant");
|
|
74
73
|
const tenantIds = await cdsHelper.getAllTenantIds();
|
|
75
74
|
await _checkPeriodicEventUpdate(tenantIds);
|
|
76
|
-
|
|
77
|
-
const runId = await _acquireRunId(emptyContext);
|
|
78
|
-
|
|
79
|
-
if (!runId) {
|
|
80
|
-
logger.error("could not acquire runId, skip processing events!");
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return await _executeEventsAllTenants(tenantIds, runId);
|
|
75
|
+
return await _executeEventsAllTenantsRedis(tenantIds);
|
|
85
76
|
} catch (err) {
|
|
86
77
|
logger.info("executing event queue run for multi instance and tenant failed", err);
|
|
87
78
|
}
|
|
88
79
|
};
|
|
89
80
|
|
|
90
81
|
const _checkPeriodicEventUpdate = async (tenantIds) => {
|
|
91
|
-
const hash = hashStringTo32Bit(JSON.stringify(tenantIds));
|
|
82
|
+
const hash = common.hashStringTo32Bit(JSON.stringify(tenantIds));
|
|
92
83
|
if (!tenantIdHash) {
|
|
93
84
|
tenantIdHash = hash;
|
|
94
85
|
return await _multiTenancyPeriodicEvents(tenantIds).catch((err) => {
|
|
@@ -104,80 +95,113 @@ const _checkPeriodicEventUpdate = async (tenantIds) => {
|
|
|
104
95
|
}
|
|
105
96
|
};
|
|
106
97
|
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
98
|
+
const _executeEventsAllTenantsRedis = async (tenantIds) => {
|
|
99
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
100
|
+
try {
|
|
101
|
+
// NOTE: do checks for all tenants on the same app instance --> acquire lock tenant independent
|
|
102
|
+
// distribute from this instance to all others
|
|
103
|
+
const dummyContext = new cds.EventContext({});
|
|
104
|
+
const couldAcquireLock = await distributedLock.acquireLock(dummyContext, EVENT_QUEUE_RUN_REDIS_CHECK, {
|
|
105
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
106
|
+
tenantScoped: false,
|
|
112
107
|
});
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
if (!couldAcquireLock) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
115
111
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
|
|
132
|
-
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
133
|
-
});
|
|
134
|
-
if (!couldAcquireLock) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
await runEventCombinationForTenant(context, eventConfig.type, eventConfig.subType, true);
|
|
138
|
-
} catch (err) {
|
|
139
|
-
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
140
|
-
tenantId,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
112
|
+
for (const tenantId of tenantIds) {
|
|
113
|
+
await cds.tx({ tenant: tenantId }, async (tx) => {
|
|
114
|
+
const entries = await openEvents.getOpenQueueEntries(tx);
|
|
115
|
+
logger.info("broadcasting events for run", {
|
|
116
|
+
tenantId,
|
|
117
|
+
entries: entries.length,
|
|
118
|
+
});
|
|
119
|
+
if (!entries.length) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
await redisPub.broadcastEvent(tenantId, entries).catch((err) => {
|
|
123
|
+
logger.error("broadcasting event failed", err, {
|
|
124
|
+
tenantId,
|
|
125
|
+
entries: entries.length,
|
|
126
|
+
});
|
|
143
127
|
});
|
|
144
128
|
});
|
|
145
|
-
}
|
|
146
|
-
)
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
logger.info("executing event queue run for multi instance and tenant failed", err);
|
|
132
|
+
}
|
|
147
133
|
};
|
|
148
134
|
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
135
|
+
const _executeEventsAllTenants = async (tenantIds, runId) => {
|
|
136
|
+
const promises = [];
|
|
137
|
+
|
|
138
|
+
for (const tenantId of tenantIds) {
|
|
139
|
+
const user = new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(tenantId) });
|
|
140
|
+
const tenantContext = {
|
|
141
|
+
tenant: tenantId,
|
|
142
|
+
user,
|
|
143
|
+
};
|
|
144
|
+
const events = await cds.tx(tenantContext, async (tx) => {
|
|
145
|
+
return await openEvents.getOpenQueueEntries(tx);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (!events.length) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
163
151
|
|
|
152
|
+
promises.concat(
|
|
153
|
+
events.map(async (openEvent) => {
|
|
154
|
+
const eventConfig = config.getEventConfig(openEvent.type, openEvent.subType);
|
|
155
|
+
const label = `${eventConfig.type}_${eventConfig.subType}`;
|
|
156
|
+
return await WorkerQueue.instance.addToQueue(eventConfig.load, label, eventConfig.priority, async () => {
|
|
164
157
|
return await cds.tx(tenantContext, async ({ context }) => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
158
|
+
try {
|
|
159
|
+
const lockId = `${runId}_${label}`;
|
|
160
|
+
const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
|
|
161
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
162
|
+
});
|
|
163
|
+
if (!couldAcquireLock) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
await runEventCombinationForTenant(context, eventConfig.type, eventConfig.subType, true);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
169
|
+
tenantId,
|
|
170
|
+
});
|
|
170
171
|
}
|
|
171
|
-
await _checkPeriodicEventsSingleTenant(context);
|
|
172
172
|
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
});
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return Promise.allSettled(promises);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const _executePeriodicEventsAllTenants = async (tenantIds) => {
|
|
181
|
+
for (const tenantId of tenantIds) {
|
|
182
|
+
try {
|
|
183
|
+
const user = new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(tenantId) });
|
|
184
|
+
const tenantContext = {
|
|
185
|
+
tenant: tenantId,
|
|
186
|
+
user,
|
|
187
|
+
};
|
|
188
|
+
await cds.tx(tenantContext, async ({ context }) => {
|
|
189
|
+
if (!config.redisEnabled) {
|
|
190
|
+
const couldAcquireLock = await distributedLock.acquireLock(context, EVENT_QUEUE_UPDATE_PERIODIC_EVENTS, {
|
|
191
|
+
expiryTime: eventQueueConfig.runInterval * 0.95,
|
|
176
192
|
});
|
|
193
|
+
if (!couldAcquireLock) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
177
196
|
}
|
|
197
|
+
await _checkPeriodicEventsSingleTenant(context);
|
|
178
198
|
});
|
|
179
|
-
})
|
|
180
|
-
|
|
199
|
+
} catch (err) {
|
|
200
|
+
cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
|
|
201
|
+
tenantId,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
181
205
|
};
|
|
182
206
|
|
|
183
207
|
const _singleTenantDb = async (tenantId) => {
|
|
@@ -269,30 +293,6 @@ const _calculateOffsetForFirstRun = async () => {
|
|
|
269
293
|
return offsetDependingOnLastRun;
|
|
270
294
|
};
|
|
271
295
|
|
|
272
|
-
const runEventCombinationForTenant = async (context, type, subType, skipWorkerPool) => {
|
|
273
|
-
try {
|
|
274
|
-
if (skipWorkerPool) {
|
|
275
|
-
return await processEventQueue(context, type, subType);
|
|
276
|
-
} else {
|
|
277
|
-
const eventConfig = eventQueueConfig.getEventConfig(type, subType);
|
|
278
|
-
const label = `${type}_${subType}`;
|
|
279
|
-
return await WorkerQueue.instance.addToQueue(
|
|
280
|
-
eventConfig.load,
|
|
281
|
-
label,
|
|
282
|
-
eventConfig.priority,
|
|
283
|
-
AsyncResource.bind(async () => await processEventQueue(context, type, subType))
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
} catch (err) {
|
|
287
|
-
const logger = cds.log(COMPONENT_NAME);
|
|
288
|
-
logger.error("error executing event combination for tenant", err, {
|
|
289
|
-
tenantId: context.tenant,
|
|
290
|
-
type,
|
|
291
|
-
subType,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
296
|
const _multiTenancyDb = async () => {
|
|
297
297
|
const logger = cds.log(COMPONENT_NAME);
|
|
298
298
|
try {
|
|
@@ -309,14 +309,29 @@ const _multiTenancyPeriodicEvents = async (tenantIds) => {
|
|
|
309
309
|
const logger = cds.log(COMPONENT_NAME);
|
|
310
310
|
try {
|
|
311
311
|
logger.info("executing event queue update periodic events");
|
|
312
|
+
|
|
313
|
+
if (config.redisEnabled) {
|
|
314
|
+
const dummyContext = new cds.EventContext({});
|
|
315
|
+
const couldAcquireLock = await distributedLock.acquireLock(dummyContext, EVENT_QUEUE_UPDATE_PERIODIC_EVENTS, {
|
|
316
|
+
expiryTime: 60 * 1000, // short living lock --> assume we do not have 2 onboards within 1 minute
|
|
317
|
+
tenantScoped: false,
|
|
318
|
+
});
|
|
319
|
+
if (!couldAcquireLock) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
312
324
|
tenantIds = tenantIds ?? (await cdsHelper.getAllTenantIds());
|
|
313
|
-
return await _executePeriodicEventsAllTenants(tenantIds
|
|
325
|
+
return await _executePeriodicEventsAllTenants(tenantIds);
|
|
314
326
|
} catch (err) {
|
|
315
327
|
logger.error("Couldn't fetch tenant ids for updating periodic event processing!", err);
|
|
316
328
|
}
|
|
317
329
|
};
|
|
318
330
|
|
|
319
|
-
const
|
|
331
|
+
const _checkPeriodicEventsSingleTenantOneTime = () =>
|
|
332
|
+
cds.tx({}, async (tx) => await periodicEvents.checkAndInsertPeriodicEvents(tx.context));
|
|
333
|
+
|
|
334
|
+
const _checkPeriodicEventsSingleTenant = async (context) => {
|
|
320
335
|
const logger = cds.log(COMPONENT_NAME);
|
|
321
336
|
if (!eventQueueConfig.updatePeriodicEvents || !eventQueueConfig.periodicEvents.length) {
|
|
322
337
|
logger.info("updating of periodic events is disabled or no periodic events configured", {
|
|
@@ -328,11 +343,9 @@ const _checkPeriodicEventsSingleTenant = async (context = {}) => {
|
|
|
328
343
|
try {
|
|
329
344
|
logger.info("executing updating periodic events", {
|
|
330
345
|
tenantId: context.tenant,
|
|
331
|
-
subdomain: context.
|
|
332
|
-
});
|
|
333
|
-
await cdsHelper.executeInNewTransaction(context, "update-periodic-events", async (tx) => {
|
|
334
|
-
await periodicEvents.checkAndInsertPeriodicEvents(tx.context);
|
|
346
|
+
subdomain: context.user?.authInfo?.getSubdomain(),
|
|
335
347
|
});
|
|
348
|
+
await periodicEvents.checkAndInsertPeriodicEvents(context);
|
|
336
349
|
} catch (err) {
|
|
337
350
|
logger.error("Couldn't update periodic events for tenant! Next try after defined interval.", err, {
|
|
338
351
|
tenantId: context.tenant,
|
|
@@ -345,7 +358,6 @@ module.exports = {
|
|
|
345
358
|
singleTenant,
|
|
346
359
|
multiTenancyDb,
|
|
347
360
|
multiTenancyRedis,
|
|
348
|
-
runEventCombinationForTenant,
|
|
349
361
|
__: {
|
|
350
362
|
_singleTenantDb,
|
|
351
363
|
_multiTenancyRedis,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { AsyncResource } = require("async_hooks");
|
|
4
|
+
|
|
5
|
+
const cds = require("@sap/cds");
|
|
6
|
+
|
|
7
|
+
const { processEventQueue } = require("../processEventQueue");
|
|
8
|
+
const eventQueueConfig = require("../config");
|
|
9
|
+
const WorkerQueue = require("../shared/WorkerQueue");
|
|
10
|
+
|
|
11
|
+
const COMPONENT_NAME = "/eventQueue/runnerHelper";
|
|
12
|
+
|
|
13
|
+
const runEventCombinationForTenant = async (context, type, subType, skipWorkerPool) => {
|
|
14
|
+
try {
|
|
15
|
+
if (skipWorkerPool) {
|
|
16
|
+
return await processEventQueue(context, type, subType);
|
|
17
|
+
} else {
|
|
18
|
+
const eventConfig = eventQueueConfig.getEventConfig(type, subType);
|
|
19
|
+
const label = `${type}_${subType}`;
|
|
20
|
+
return await WorkerQueue.instance.addToQueue(
|
|
21
|
+
eventConfig.load,
|
|
22
|
+
label,
|
|
23
|
+
eventConfig.priority,
|
|
24
|
+
AsyncResource.bind(async () => await processEventQueue(context, type, subType))
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
} catch (err) {
|
|
28
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
29
|
+
logger.error("error executing event combination for tenant", err, {
|
|
30
|
+
tenantId: context.tenant,
|
|
31
|
+
type,
|
|
32
|
+
subType,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
module.exports = { runEventCombinationForTenant };
|
package/src/shared/cdsHelper.js
CHANGED
|
@@ -4,8 +4,7 @@ const VError = require("verror");
|
|
|
4
4
|
const cds = require("@sap/cds");
|
|
5
5
|
|
|
6
6
|
const config = require("../config");
|
|
7
|
-
|
|
8
|
-
const subdomainCache = {};
|
|
7
|
+
const common = require("./common");
|
|
9
8
|
|
|
10
9
|
const VERROR_CLUSTER_NAME = "ExecuteInNewTransactionError";
|
|
11
10
|
const COMPONENT_NAME = "/eventQueue/cdsHelper";
|
|
@@ -24,7 +23,7 @@ async function executeInNewTransaction(context = {}, transactionTag, fn, args, {
|
|
|
24
23
|
const parameters = Array.isArray(args) ? args : [args];
|
|
25
24
|
const logger = cds.log(COMPONENT_NAME);
|
|
26
25
|
try {
|
|
27
|
-
const user = new cds.User.Privileged(config.userId);
|
|
26
|
+
const user = new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(context.tenant) });
|
|
28
27
|
if (cds.db.kind === "hana") {
|
|
29
28
|
await cds.tx(
|
|
30
29
|
{
|
|
@@ -33,7 +32,6 @@ async function executeInNewTransaction(context = {}, transactionTag, fn, args, {
|
|
|
33
32
|
locale: context.locale,
|
|
34
33
|
user,
|
|
35
34
|
headers: context.headers,
|
|
36
|
-
http: context.http,
|
|
37
35
|
},
|
|
38
36
|
async (tx) => {
|
|
39
37
|
tx.context._ = context._ ?? {};
|
|
@@ -51,7 +49,6 @@ async function executeInNewTransaction(context = {}, transactionTag, fn, args, {
|
|
|
51
49
|
locale: context.locale,
|
|
52
50
|
user,
|
|
53
51
|
headers: context.headers,
|
|
54
|
-
http: context.http,
|
|
55
52
|
},
|
|
56
53
|
async (tx) => fn(tx, ...parameters)
|
|
57
54
|
);
|
|
@@ -102,33 +99,19 @@ class TriggerRollback extends VError {
|
|
|
102
99
|
}
|
|
103
100
|
}
|
|
104
101
|
|
|
105
|
-
const
|
|
102
|
+
const getAllTenantIds = async () => {
|
|
106
103
|
if (!config.isMultiTenancy) {
|
|
107
104
|
return null;
|
|
108
105
|
}
|
|
109
|
-
if (subdomainCache[tenantId]) {
|
|
110
|
-
return await subdomainCache[tenantId];
|
|
111
|
-
}
|
|
112
|
-
subdomainCache[tenantId] = new Promise((resolve) => {
|
|
113
|
-
cds.connect
|
|
114
|
-
.to("cds.xt.SaasProvisioningService")
|
|
115
|
-
.then((ssp) => {
|
|
116
|
-
ssp
|
|
117
|
-
.get("/tenant", { subscribedTenantId: tenantId })
|
|
118
|
-
.then((response) => {
|
|
119
|
-
resolve(response.subscribedSubdomain);
|
|
120
|
-
})
|
|
121
|
-
.catch(() => resolve(null));
|
|
122
|
-
})
|
|
123
|
-
.catch(() => resolve(null));
|
|
124
|
-
});
|
|
125
|
-
return await subdomainCache[tenantId];
|
|
126
|
-
};
|
|
127
106
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
107
|
+
// NOTE: tmp workaround until cds-mtxs fixes the connect.to service
|
|
108
|
+
for (let i = 0; i < 10; i++) {
|
|
109
|
+
if (cds.services["saas-registry"]) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
131
113
|
}
|
|
114
|
+
|
|
132
115
|
const ssp = await cds.connect.to("cds.xt.SaasProvisioningService");
|
|
133
116
|
const response = await ssp.get("/tenant");
|
|
134
117
|
return response
|
|
@@ -141,6 +124,5 @@ const isFakeTenant = (tenantId) => /00000000-0000-4000-8000-\d{12}/.test(tenantI
|
|
|
141
124
|
module.exports = {
|
|
142
125
|
executeInNewTransaction,
|
|
143
126
|
TriggerRollback,
|
|
144
|
-
getSubdomainForTenantId,
|
|
145
127
|
getAllTenantIds,
|
|
146
128
|
};
|
package/src/shared/common.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const crypto = require("crypto");
|
|
4
|
+
const { promisify } = require("util");
|
|
5
|
+
|
|
6
|
+
const cds = require("@sap/cds");
|
|
7
|
+
const xssec = require("@sap/xssec");
|
|
8
|
+
|
|
9
|
+
const getAuthTokenAsync = promisify(xssec.requests.requestClientCredentialsToken);
|
|
10
|
+
const getCreateSecurityContextAsync = promisify(xssec.createSecurityContext);
|
|
11
|
+
|
|
12
|
+
let authInfoCache = {};
|
|
13
|
+
const MARGIN_AUTH_INFO_EXPIRY = 60 * 1000;
|
|
14
|
+
const COMPONENT_NAME = "/eventQueue/common";
|
|
15
|
+
|
|
4
16
|
const arrayToFlatMap = (array, key = "ID") => {
|
|
5
17
|
return array.reduce((result, element) => {
|
|
6
18
|
result[element[key]] = element;
|
|
@@ -61,4 +73,43 @@ const processChunkedSync = (inputs, chunkSize, chunkHandler) => {
|
|
|
61
73
|
|
|
62
74
|
const hashStringTo32Bit = (value) => crypto.createHash("sha256").update(String(value)).digest("base64").slice(0, 32);
|
|
63
75
|
|
|
64
|
-
|
|
76
|
+
const _getNewAuthInfo = async (tenantId) => {
|
|
77
|
+
try {
|
|
78
|
+
const token = await getAuthTokenAsync(null, cds.requires.auth.credentials, null, tenantId);
|
|
79
|
+
const authInfo = await getCreateSecurityContextAsync(token, cds.requires.auth.credentials);
|
|
80
|
+
authInfoCache[tenantId].expireTs = authInfo.getExpirationDate().getTime() - MARGIN_AUTH_INFO_EXPIRY;
|
|
81
|
+
return authInfo;
|
|
82
|
+
} catch (err) {
|
|
83
|
+
authInfoCache[tenantId] = null;
|
|
84
|
+
cds.log(COMPONENT_NAME).warn("failed to request authInfo", err);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const getAuthInfo = async (tenantId) => {
|
|
89
|
+
if (!cds.requires?.auth?.credentials) {
|
|
90
|
+
return null; // no credentials not authInfo
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// not existing or existing but expired
|
|
94
|
+
if (
|
|
95
|
+
!authInfoCache[tenantId] ||
|
|
96
|
+
(authInfoCache[tenantId] && authInfoCache[tenantId].expireTs && Date.now() > authInfoCache[tenantId].expireTs)
|
|
97
|
+
) {
|
|
98
|
+
authInfoCache[tenantId] ??= {};
|
|
99
|
+
authInfoCache[tenantId].value = _getNewAuthInfo(tenantId);
|
|
100
|
+
authInfoCache[tenantId].expireTs = null;
|
|
101
|
+
}
|
|
102
|
+
return await authInfoCache[tenantId].value;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
arrayToFlatMap,
|
|
107
|
+
limiter,
|
|
108
|
+
isValidDate,
|
|
109
|
+
processChunkedSync,
|
|
110
|
+
hashStringTo32Bit,
|
|
111
|
+
getAuthInfo,
|
|
112
|
+
__: {
|
|
113
|
+
clearAuthInfoCache: () => (authInfoCache = {}),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -54,7 +54,7 @@ const checkLockExistsAndReturnValue = async (context, key, { tenantScoped = true
|
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
const _acquireLockRedis = async (context, fullKey, expiryTime, { value = "true", overrideValue = false } = {}) => {
|
|
57
|
-
const client = await redis.createMainClientAndConnect();
|
|
57
|
+
const client = await redis.createMainClientAndConnect(config.redisOptions);
|
|
58
58
|
const result = await client.set(fullKey, value, {
|
|
59
59
|
PX: expiryTime,
|
|
60
60
|
...(overrideValue ? null : { NX: true }),
|
|
@@ -63,7 +63,7 @@ const _acquireLockRedis = async (context, fullKey, expiryTime, { value = "true",
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
const _checkLockExistsRedis = async (context, fullKey) => {
|
|
66
|
-
const client = await redis.createMainClientAndConnect();
|
|
66
|
+
const client = await redis.createMainClientAndConnect(config.redisOptions);
|
|
67
67
|
return await client.get(fullKey);
|
|
68
68
|
};
|
|
69
69
|
|
|
@@ -76,7 +76,7 @@ const _checkLockExistsDb = async (context, fullKey) => {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
const _releaseLockRedis = async (context, fullKey) => {
|
|
79
|
-
const client = await redis.createMainClientAndConnect();
|
|
79
|
+
const client = await redis.createMainClientAndConnect(config.redisOptions);
|
|
80
80
|
await client.del(fullKey);
|
|
81
81
|
};
|
|
82
82
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const cds = require("@sap/cds");
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const redisPub = require("../redis/redisPub");
|
|
6
6
|
const config = require("./../config");
|
|
7
7
|
|
|
8
8
|
const COMPONENT_NAME = "/eventQueue/shared/eventScheduler";
|
|
@@ -26,7 +26,7 @@ class EventScheduler {
|
|
|
26
26
|
});
|
|
27
27
|
setTimeout(() => {
|
|
28
28
|
delete this.#scheduledEvents[key];
|
|
29
|
-
broadcastEvent(tenantId, type, subType).catch((err) => {
|
|
29
|
+
redisPub.broadcastEvent(tenantId, { type, subType }).catch((err) => {
|
|
30
30
|
cds.log(COMPONENT_NAME).error("could not execute scheduled event", err, {
|
|
31
31
|
tenantId,
|
|
32
32
|
type,
|