soajs.controller 4.3.18 → 4.3.20
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/.github/workflows/ci.yml +4 -1
- package/.github/workflows/release.yml +4 -1
- package/docs/maintenance-routes.md +31 -0
- package/docs/middleware/cache.md +74 -1
- package/docs/middleware/idempotency.md +31 -2
- package/mw/cache/index.js +24 -5
- package/mw/cache/invalidate.js +71 -0
- package/mw/cache/model/mongo.js +95 -1
- package/mw/gotoService/redirectToService.js +21 -9
- package/mw/idempotency/index.js +42 -4
- package/mw/idempotency/model/memory.js +12 -2
- package/mw/idempotency/model/mongo.js +10 -3
- package/package.json +1 -1
- package/server/maintenance.js +41 -0
- package/test/data/provision/customRegistry/gateway.js +47 -0
- package/test/data/provision/environment/dev.js +9 -0
- package/test/data/provision/hosts/idempotent.js +12 -0
- package/test/data/provision/hosts/spaces.js +12 -0
- package/test/data/provision/marketplace/idempotent.js +37 -0
- package/test/data/provision/marketplace/spaces.js +36 -0
- package/test/data/provision/oauth/token/second_owner_token.js +72 -0
- package/test/data/provision/products/dsbrd.js +49 -0
- package/test/data/provision/urac/users/second_owner.js +61 -0
- package/test/integration/UC6-idempotency/index.js +197 -0
- package/test/integration/UC7-cache-invalidate/index.js +209 -0
- package/test/integration/index.js +8 -0
- package/test/unit/index.js +3 -0
- package/test/unit/mw/cache/index.js +122 -0
- package/test/unit/mw/cache/invalidate.js +99 -0
- package/test/unit/mw/cache/mongo.js +104 -0
- package/test/unit/mw/idempotency/index.js +122 -0
- package/test/unit/mw/idempotency/memory.js +36 -4
- package/test/unit/mw/idempotency/mongo.js +62 -0
|
@@ -75,7 +75,7 @@ let model = {
|
|
|
75
75
|
return entry;
|
|
76
76
|
},
|
|
77
77
|
|
|
78
|
-
'lock': (key, ttl) => {
|
|
78
|
+
'lock': (key, ttl, lockId) => {
|
|
79
79
|
if (!dataHolder[key.l1]) {
|
|
80
80
|
dataHolder[key.l1] = {};
|
|
81
81
|
}
|
|
@@ -85,6 +85,7 @@ let model = {
|
|
|
85
85
|
}
|
|
86
86
|
dataHolder[key.l1][key.l2] = {
|
|
87
87
|
'status': 'in_flight',
|
|
88
|
+
'lockId': lockId || null,
|
|
88
89
|
'lockedAt': Date.now(),
|
|
89
90
|
'expiresAt': Date.now() + ttl
|
|
90
91
|
};
|
|
@@ -103,8 +104,17 @@ let model = {
|
|
|
103
104
|
};
|
|
104
105
|
},
|
|
105
106
|
|
|
106
|
-
|
|
107
|
+
//NOTE: only an entry still in flight is removed, never a completed one, and with a lockId only
|
|
108
|
+
// the lock of that request, not one taken by a retry after the ttl
|
|
109
|
+
'unlock': (key, lockId) => {
|
|
107
110
|
if (dataHolder[key.l1] && dataHolder[key.l1][key.l2]) {
|
|
111
|
+
const entry = dataHolder[key.l1][key.l2];
|
|
112
|
+
if (entry.status !== 'in_flight') {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (lockId && entry.lockId !== lockId) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
108
118
|
delete dataHolder[key.l1][key.l2];
|
|
109
119
|
}
|
|
110
120
|
},
|
|
@@ -58,7 +58,7 @@ let model = {
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
|
|
61
|
-
'lock': async (key, ttl) => {
|
|
61
|
+
'lock': async (key, ttl, lockId) => {
|
|
62
62
|
let condition = {
|
|
63
63
|
'l1': key.l1,
|
|
64
64
|
'l2': key.l2
|
|
@@ -73,6 +73,7 @@ let model = {
|
|
|
73
73
|
'l1': key.l1,
|
|
74
74
|
'l2': key.l2,
|
|
75
75
|
'status': 'in_flight',
|
|
76
|
+
'lockId': lockId || null,
|
|
76
77
|
'lockedAt': new Date(),
|
|
77
78
|
'expiresAt': new Date(Date.now() + ttl)
|
|
78
79
|
};
|
|
@@ -108,11 +109,17 @@ let model = {
|
|
|
108
109
|
}
|
|
109
110
|
},
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
//NOTE: only a record still in flight is removed, never a completed one, and with a lockId only
|
|
113
|
+
// the lock of that request, not one taken by a retry after the ttl
|
|
114
|
+
'unlock': async (key, lockId) => {
|
|
112
115
|
let condition = {
|
|
113
116
|
'l1': key.l1,
|
|
114
|
-
'l2': key.l2
|
|
117
|
+
'l2': key.l2,
|
|
118
|
+
'status': 'in_flight'
|
|
115
119
|
};
|
|
120
|
+
if (lockId) {
|
|
121
|
+
condition.lockId = lockId;
|
|
122
|
+
}
|
|
116
123
|
try {
|
|
117
124
|
await model.mongo.deleteOne(idempotency_store, condition);
|
|
118
125
|
} catch (e) {
|
package/package.json
CHANGED
package/server/maintenance.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
const get = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o);
|
|
12
12
|
|
|
13
13
|
const registryModule = require("./../modules/registry");
|
|
14
|
+
const cacheInvalidate = require("./../mw/cache/invalidate.js");
|
|
14
15
|
|
|
15
16
|
const httpProxy = require('http-proxy');
|
|
16
17
|
const http = require('http');
|
|
@@ -311,6 +312,46 @@ let Maintenance = (core, log, param, serviceIp, regEnvironment, awareness_mw, so
|
|
|
311
312
|
};
|
|
312
313
|
proxy.web(req, res, {target: haTarget});
|
|
313
314
|
}
|
|
315
|
+
else if (parsedUrl.pathname === '/cache/invalidate') {
|
|
316
|
+
let response = maintenanceResponse(parsedUrl, param);
|
|
317
|
+
let reply = (status, extra) => {
|
|
318
|
+
res.writeHead(status, {'Content-Type': 'application/json'});
|
|
319
|
+
return res.end(JSON.stringify(Object.assign(response, extra)));
|
|
320
|
+
};
|
|
321
|
+
if (req.method !== 'POST') {
|
|
322
|
+
return reply(405, {"error": "Use POST."});
|
|
323
|
+
}
|
|
324
|
+
let body = "";
|
|
325
|
+
let tooLarge = false;
|
|
326
|
+
req.on('data', function (chunk) {
|
|
327
|
+
body += chunk;
|
|
328
|
+
//NOTE: the body only names what to remove, anything larger is not a valid request
|
|
329
|
+
if (body.length > 16384) {
|
|
330
|
+
tooLarge = true;
|
|
331
|
+
req.destroy();
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
req.on('end', function () {
|
|
335
|
+
if (tooLarge) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
let input = null;
|
|
339
|
+
try {
|
|
340
|
+
input = body ? JSON.parse(body) : null;
|
|
341
|
+
} catch (e) {
|
|
342
|
+
return reply(400, {"error": "The body is not valid JSON."});
|
|
343
|
+
}
|
|
344
|
+
cacheInvalidate(registryModule.get(), input).then((outcome) => {
|
|
345
|
+
if (outcome.status !== 200) {
|
|
346
|
+
return reply(outcome.status, {"error": outcome.error});
|
|
347
|
+
}
|
|
348
|
+
log.info("Cache invalidated for tenant [" + input.tenant + "] service [" + input.service + "]" +
|
|
349
|
+
(input.api ? " api [" + input.api + "]" : "") + (input.user ? " user [" + input.user + "]" : "") +
|
|
350
|
+
", removed [" + outcome.removed + "]");
|
|
351
|
+
return reply(200, {"result": true, "data": {"removed": outcome.removed}});
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
}
|
|
314
355
|
else if (parsedUrl.pathname === '/loadProvision') {
|
|
315
356
|
provision.loadProvision(function (loaded) {
|
|
316
357
|
let response = maintenanceResponse(parsedUrl, param);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
let doc = {
|
|
3
|
+
"_id": "5d7f8e43edf21900fed08cf7",
|
|
4
|
+
"name": "gateway",
|
|
5
|
+
"locked": false,
|
|
6
|
+
"plugged": true,
|
|
7
|
+
"shared": false,
|
|
8
|
+
"value": {
|
|
9
|
+
"idempotency": {
|
|
10
|
+
"model": "mongo",
|
|
11
|
+
"services": {
|
|
12
|
+
"idempotent": {
|
|
13
|
+
"enabled": true,
|
|
14
|
+
"ttl": 60000,
|
|
15
|
+
"apis": ["POST /ok", "POST /fail", "POST /bad", "POST /slow", "POST /throttled"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"cache": {
|
|
20
|
+
"model": "mongo",
|
|
21
|
+
"defaultTTL": 60000,
|
|
22
|
+
"services": {
|
|
23
|
+
"spaces": {
|
|
24
|
+
"enabled": true,
|
|
25
|
+
"apis": {
|
|
26
|
+
"GET /spaces": { "enabled": true, "scope": "tenant_user" },
|
|
27
|
+
"GET /space/:id": { "enabled": true, "scope": "tenant_user" },
|
|
28
|
+
"GET /slow": { "enabled": true, "scope": "tenant_user" },
|
|
29
|
+
"GET /catalog": { "enabled": true, "scope": "tenant" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"traffic": {
|
|
35
|
+
"throttling": {
|
|
36
|
+
"idempotent": {
|
|
37
|
+
"publicAPIStrategy": "idempotent_tight",
|
|
38
|
+
"apis": ["/throttled"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"created": "DEV",
|
|
44
|
+
"author": "owner"
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
module.exports = doc;
|
|
@@ -115,6 +115,15 @@ let env = {
|
|
|
115
115
|
"accessTokenLifetime": 7200,
|
|
116
116
|
"refreshTokenLifetime": 1209600
|
|
117
117
|
},
|
|
118
|
+
"throttling": {
|
|
119
|
+
"idempotent_tight": {
|
|
120
|
+
"type": 1,
|
|
121
|
+
"window": 60000,
|
|
122
|
+
"limit": 1,
|
|
123
|
+
"retries": 0,
|
|
124
|
+
"delay": 100
|
|
125
|
+
}
|
|
126
|
+
},
|
|
118
127
|
"ports": {
|
|
119
128
|
"controller": 4000,
|
|
120
129
|
"maintenanceInc": 1001,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
let service = {
|
|
4
|
+
_id: "5da078c92357684b57effcf2",
|
|
5
|
+
"type": "service",
|
|
6
|
+
name: "idempotent",
|
|
7
|
+
configuration: {
|
|
8
|
+
group: "SOAJS test",
|
|
9
|
+
port: 4014,
|
|
10
|
+
requestTimeout: 30,
|
|
11
|
+
requestTimeoutRenewal: 5
|
|
12
|
+
},
|
|
13
|
+
versions: [{
|
|
14
|
+
"version": "1",
|
|
15
|
+
maintenance: {
|
|
16
|
+
"port": {
|
|
17
|
+
"type": "maintenance"
|
|
18
|
+
},
|
|
19
|
+
"readiness": "/heartbeat"
|
|
20
|
+
},
|
|
21
|
+
oauth: true,
|
|
22
|
+
extKeyRequired: true,
|
|
23
|
+
urac: true,
|
|
24
|
+
urac_Profile: false,
|
|
25
|
+
urac_ACL: false,
|
|
26
|
+
provision_ACL: false,
|
|
27
|
+
apis: [
|
|
28
|
+
{ l: "Answers 200", v: "/ok", m: "post", group: "Integration test" },
|
|
29
|
+
{ l: "Answers 503 the first time", v: "/fail", m: "post", group: "Integration test" },
|
|
30
|
+
{ l: "Answers 400", v: "/bad", m: "post", group: "Integration test" },
|
|
31
|
+
{ l: "Answers 200 after a delay", v: "/slow", m: "post", group: "Integration test" },
|
|
32
|
+
{ l: "Answers 200, throttled", v: "/throttled", m: "post", group: "Integration test" }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
module.exports = service;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
let service = {
|
|
4
|
+
_id: "5da078c92357684b57effcf4",
|
|
5
|
+
"type": "service",
|
|
6
|
+
name: "spaces",
|
|
7
|
+
configuration: {
|
|
8
|
+
group: "SOAJS test",
|
|
9
|
+
port: 4015,
|
|
10
|
+
requestTimeout: 30,
|
|
11
|
+
requestTimeoutRenewal: 5
|
|
12
|
+
},
|
|
13
|
+
versions: [{
|
|
14
|
+
"version": "1",
|
|
15
|
+
maintenance: {
|
|
16
|
+
"port": {
|
|
17
|
+
"type": "maintenance"
|
|
18
|
+
},
|
|
19
|
+
"readiness": "/heartbeat"
|
|
20
|
+
},
|
|
21
|
+
oauth: true,
|
|
22
|
+
extKeyRequired: true,
|
|
23
|
+
urac: true,
|
|
24
|
+
urac_Profile: false,
|
|
25
|
+
urac_ACL: false,
|
|
26
|
+
provision_ACL: false,
|
|
27
|
+
apis: [
|
|
28
|
+
{ l: "Spaces of the user", v: "/spaces", m: "get", group: "Integration test" },
|
|
29
|
+
{ l: "One space", v: "/space/:id", m: "get", group: "Integration test" },
|
|
30
|
+
{ l: "Spaces after a delay", v: "/slow", m: "get", group: "Integration test" },
|
|
31
|
+
{ l: "Same for the whole tenant", v: "/catalog", m: "get", group: "Integration test" }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
};
|
|
36
|
+
module.exports = service;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
let token = {
|
|
4
|
+
_id: "5cdc190fd52c82e0ddb1dcd8",
|
|
5
|
+
type: "accessToken",
|
|
6
|
+
token: "c4c4e5e5f6f6a7a7b8b8c9c9d0d0e1e1f2f2a301",
|
|
7
|
+
clientId: "5c0e74ba9acc3c5a84a51259",
|
|
8
|
+
user: {
|
|
9
|
+
_id: "5c8d0c505653de3985aa2001",
|
|
10
|
+
locked: true,
|
|
11
|
+
username: "second_owner",
|
|
12
|
+
firstName: "owner3'sèį",
|
|
13
|
+
lastName: "owner",
|
|
14
|
+
email: "me@localhost.com",
|
|
15
|
+
"ts": new Date().getTime(),
|
|
16
|
+
status: "active",
|
|
17
|
+
profile: {},
|
|
18
|
+
groups: [
|
|
19
|
+
"owner"
|
|
20
|
+
],
|
|
21
|
+
config: {
|
|
22
|
+
packages: {},
|
|
23
|
+
keys: {},
|
|
24
|
+
allowedTenants: [
|
|
25
|
+
{
|
|
26
|
+
tenant: {
|
|
27
|
+
id: "THYME_tID",
|
|
28
|
+
code: "THYME_CODE",
|
|
29
|
+
pin: {
|
|
30
|
+
code: "5678",
|
|
31
|
+
allowed: true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
groups: [
|
|
35
|
+
"waiter"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
tenant: {
|
|
40
|
+
id: "ELVIRA_tID",
|
|
41
|
+
code: "ELVIRA_CODE"
|
|
42
|
+
},
|
|
43
|
+
groups: [
|
|
44
|
+
"manager"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
tenant: {
|
|
50
|
+
id: "5c0e74ba9acc3c5a84a51259",
|
|
51
|
+
code: "DBTN",
|
|
52
|
+
pin: {
|
|
53
|
+
code: "1235",
|
|
54
|
+
allowed: true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
lastLogin: new Date().getTime(),
|
|
58
|
+
groupsConfig: {
|
|
59
|
+
allowedPackages: {
|
|
60
|
+
DSBRD: [
|
|
61
|
+
"DSBRD_OWNER"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
allowedEnvironments: {}
|
|
65
|
+
},
|
|
66
|
+
loginMode: "urac",
|
|
67
|
+
id: "5c8d0c505653de3985aa2001"
|
|
68
|
+
},
|
|
69
|
+
env: "dev",
|
|
70
|
+
expires: new Date((new Date().getFullYear()) + 2, 0, 1)
|
|
71
|
+
};
|
|
72
|
+
module.exports = token;
|
|
@@ -9,6 +9,39 @@ let prod = {
|
|
|
9
9
|
"scope": {
|
|
10
10
|
"acl": {
|
|
11
11
|
"dev": {
|
|
12
|
+
"spaces": {
|
|
13
|
+
"1": {
|
|
14
|
+
"access": true,
|
|
15
|
+
"get": [
|
|
16
|
+
{
|
|
17
|
+
"group": "Spaces group",
|
|
18
|
+
"apis": {
|
|
19
|
+
"/spaces": {},
|
|
20
|
+
"/space/:id": {},
|
|
21
|
+
"/slow": {},
|
|
22
|
+
"/catalog": {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"idempotent": {
|
|
29
|
+
"1": {
|
|
30
|
+
"access": true,
|
|
31
|
+
"post": [
|
|
32
|
+
{
|
|
33
|
+
"group": "Idempotent group",
|
|
34
|
+
"apis": {
|
|
35
|
+
"/ok": {},
|
|
36
|
+
"/fail": {},
|
|
37
|
+
"/bad": {},
|
|
38
|
+
"/slow": {},
|
|
39
|
+
"/throttled": {}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
12
45
|
"httpmethods": {
|
|
13
46
|
"1": {
|
|
14
47
|
"access": true,
|
|
@@ -200,6 +233,22 @@ let prod = {
|
|
|
200
233
|
"locked": true,
|
|
201
234
|
"acl": {
|
|
202
235
|
"dev": {
|
|
236
|
+
"spaces": [
|
|
237
|
+
{
|
|
238
|
+
"version": "1",
|
|
239
|
+
"get": [
|
|
240
|
+
"Spaces group"
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
"idempotent": [
|
|
245
|
+
{
|
|
246
|
+
"version": "1",
|
|
247
|
+
"post": [
|
|
248
|
+
"Idempotent group"
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
],
|
|
203
252
|
"httpmethods": [
|
|
204
253
|
{
|
|
205
254
|
"version": "1",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
let user = {
|
|
3
|
+
_id: "5c8d0c505653de3985aa2001",
|
|
4
|
+
locked: true,
|
|
5
|
+
username: "second_owner",
|
|
6
|
+
password: "$2a$12$geJJfv33wkYIXEAlDkeeuOgiQ6y6MjP/YxbqLdHdDSK7LDG.7n7Pq",
|
|
7
|
+
firstName: "owner3'sèį",
|
|
8
|
+
lastName: "owner",
|
|
9
|
+
email: "second_owner@localhost.com",
|
|
10
|
+
ts: 1552747600152,
|
|
11
|
+
status: "active",
|
|
12
|
+
profile: {
|
|
13
|
+
|
|
14
|
+
},
|
|
15
|
+
groups: [
|
|
16
|
+
"owner"
|
|
17
|
+
],
|
|
18
|
+
config: {
|
|
19
|
+
packages: {
|
|
20
|
+
|
|
21
|
+
},
|
|
22
|
+
keys: {
|
|
23
|
+
|
|
24
|
+
},
|
|
25
|
+
allowedTenants: [
|
|
26
|
+
{
|
|
27
|
+
tenant: {
|
|
28
|
+
id: "THYME_tID",
|
|
29
|
+
code: "THYME_CODE",
|
|
30
|
+
pin: {
|
|
31
|
+
code: "5678",
|
|
32
|
+
allowed: true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
groups: [
|
|
36
|
+
"waiter"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
tenant: {
|
|
41
|
+
id: "ELVIRA_tID",
|
|
42
|
+
code: "ELVIRA_CODE"
|
|
43
|
+
},
|
|
44
|
+
groups: [
|
|
45
|
+
"manager"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
tenant: {
|
|
51
|
+
id: "5c0e74ba9acc3c5a84a51259",
|
|
52
|
+
code: "DBTN",
|
|
53
|
+
pin: {
|
|
54
|
+
code: "1235",
|
|
55
|
+
allowed: true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
lastLogin: 1559836949372
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
module.exports = user;
|