account-lookup-service 17.10.3 → 17.11.0-snapshot.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/default.json +14 -1
- package/docker/account-lookup-service/default.json +13 -1
- package/docker-compose.yml +2 -0
- package/package.json +6 -5
- package/src/constants.js +4 -1
- package/src/handlers/TimeoutHandler.js +9 -3
- package/src/handlers/register.js +1 -1
- package/src/lib/config.js +1 -0
- package/src/lib/createDistLock.js +74 -0
- package/src/lib/index.js +3 -1
- package/test/unit/handlers/TimeoutHandler.test.js +58 -4
package/config/default.json
CHANGED
@@ -80,7 +80,20 @@
|
|
80
80
|
"DISABLED": false,
|
81
81
|
"TIMEXP": "*/30 * * * * *",
|
82
82
|
"TIMEZONE": "UTC",
|
83
|
-
"BATCH_SIZE": 100
|
83
|
+
"BATCH_SIZE": 100,
|
84
|
+
"DIST_LOCK": {
|
85
|
+
"enabled": false,
|
86
|
+
"lockTimeout": 10000,
|
87
|
+
"acquireTimeout": 5000,
|
88
|
+
"driftFactor": 0.01,
|
89
|
+
"retryCount": 3,
|
90
|
+
"retryDelay": 200,
|
91
|
+
"retryJitter": 100,
|
92
|
+
"redisConfigs": [{
|
93
|
+
"type": "redis-cluster",
|
94
|
+
"cluster": [{ "host": "localhost", "port": 6379 }]
|
95
|
+
}]
|
96
|
+
}
|
84
97
|
}
|
85
98
|
},
|
86
99
|
"SWITCH_ENDPOINT": "http://localhost:3001",
|
@@ -79,7 +79,19 @@
|
|
79
79
|
"DISABLED": false,
|
80
80
|
"TIMEXP": "*/10 * * * * *",
|
81
81
|
"TIMEZONE": "UTC",
|
82
|
-
"BATCH_SIZE": 100
|
82
|
+
"BATCH_SIZE": 100,
|
83
|
+
"DIST_LOCK": {
|
84
|
+
"enabled": true,
|
85
|
+
"lockTimeout": 10000,
|
86
|
+
"driftFactor": 0.01,
|
87
|
+
"retryCount": 3,
|
88
|
+
"retryDelay": 200,
|
89
|
+
"retryJitter": 100,
|
90
|
+
"redisConfigs": [{
|
91
|
+
"type": "redis-cluster",
|
92
|
+
"cluster": [{ "host": "redis-node-0", "port": 6379 }]
|
93
|
+
}]
|
94
|
+
}
|
83
95
|
}
|
84
96
|
},
|
85
97
|
"SWITCH_ENDPOINT": "http://central-ledger:3001",
|
package/docker-compose.yml
CHANGED
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "account-lookup-service",
|
3
3
|
"description": "Account Lookup Service is used to validate Party and Participant lookups.",
|
4
|
-
"version": "17.
|
4
|
+
"version": "17.11.0-snapshot.0",
|
5
5
|
"license": "Apache-2.0",
|
6
6
|
"author": "ModusBox",
|
7
7
|
"contributors": [
|
@@ -68,6 +68,7 @@
|
|
68
68
|
"seed:run": "knex seed:run $npm_package_config_knex",
|
69
69
|
"seed:create": "knex seed:make $npm_package_config_knex",
|
70
70
|
"regenerate": "yo swaggerize:test --framework hapi --apiPath './config/api_swagger.json'",
|
71
|
+
"dc:up:als-toh": ". ./test/integration/env.sh && docker compose $npm_package_config_env_file up account-lookup-service-handlers",
|
71
72
|
"dc:build": "docker compose $npm_package_config_env_file build",
|
72
73
|
"dc:up": ". ./test/integration/env.sh && docker compose $npm_package_config_env_file up -d && docker ps",
|
73
74
|
"dc:down": ". ./test/integration/env.sh && docker compose $npm_package_config_env_file down -v",
|
@@ -94,13 +95,13 @@
|
|
94
95
|
"@mojaloop/central-services-health": "15.1.0",
|
95
96
|
"@mojaloop/central-services-logger": "11.9.0",
|
96
97
|
"@mojaloop/central-services-metrics": "12.6.0",
|
97
|
-
"@mojaloop/central-services-shared": "18.
|
98
|
-
"@mojaloop/central-services-stream": "11.
|
98
|
+
"@mojaloop/central-services-shared": "18.29.0-snapshot.0",
|
99
|
+
"@mojaloop/central-services-stream": "11.8.0",
|
99
100
|
"@mojaloop/database-lib": "11.2.0",
|
100
101
|
"@mojaloop/event-sdk": "14.6.1",
|
101
102
|
"@mojaloop/inter-scheme-proxy-cache-lib": "2.6.0",
|
102
103
|
"@mojaloop/ml-schema-transformer-lib": "2.7.1",
|
103
|
-
"@mojaloop/sdk-standard-components": "19.
|
104
|
+
"@mojaloop/sdk-standard-components": "19.16.0",
|
104
105
|
"@now-ims/hapi-now-auth": "2.1.0",
|
105
106
|
"ajv": "8.17.1",
|
106
107
|
"ajv-keywords": "5.1.0",
|
@@ -165,7 +166,7 @@
|
|
165
166
|
"axios": "1.10.0",
|
166
167
|
"axios-retry": "^4.5.0",
|
167
168
|
"docdash": "2.0.2",
|
168
|
-
"dotenv": "^
|
169
|
+
"dotenv": "^17.0.1",
|
169
170
|
"get-port": "5.1.1",
|
170
171
|
"ioredis-mock": "^8.9.0",
|
171
172
|
"jest": "29.7.0",
|
package/src/constants.js
CHANGED
@@ -43,8 +43,11 @@ const HANDLER_TYPES = Object.freeze({
|
|
43
43
|
TIMEOUT: 'timeout'
|
44
44
|
})
|
45
45
|
|
46
|
+
const TIMEOUT_HANDLER_DIST_LOCK_KEY = 'mutex:als-timeout-handler'
|
47
|
+
|
46
48
|
module.exports = {
|
47
49
|
API_TYPES,
|
48
50
|
ERROR_MESSAGES,
|
49
|
-
HANDLER_TYPES
|
51
|
+
HANDLER_TYPES,
|
52
|
+
TIMEOUT_HANDLER_DIST_LOCK_KEY
|
50
53
|
}
|
@@ -34,10 +34,12 @@ const CronJob = require('cron').CronJob
|
|
34
34
|
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
35
35
|
const TimeoutService = require('../domain/timeout')
|
36
36
|
const Config = require('../lib/config')
|
37
|
+
const { createDistLock } = require('../lib')
|
37
38
|
|
38
39
|
let timeoutJob
|
39
40
|
let isRegistered
|
40
41
|
let isRunning
|
42
|
+
let distLock
|
41
43
|
|
42
44
|
const timeout = async (options) => {
|
43
45
|
if (isRunning) return
|
@@ -45,24 +47,26 @@ const timeout = async (options) => {
|
|
45
47
|
|
46
48
|
try {
|
47
49
|
isRunning = true
|
50
|
+
if (!await distLock?.acquireLock()) return
|
48
51
|
await TimeoutService.timeoutInterschemePartiesLookups(options)
|
49
52
|
await TimeoutService.timeoutProxyGetPartiesLookups(options)
|
50
53
|
logger.verbose('ALS timeout handler is done')
|
51
54
|
} catch (err) {
|
52
55
|
logger.error('error in timeout: ', err)
|
53
56
|
} finally {
|
57
|
+
await distLock?.releaseLock()
|
54
58
|
isRunning = false
|
55
59
|
}
|
56
60
|
}
|
57
61
|
|
58
62
|
const register = async (options) => {
|
59
63
|
if (Config.HANDLERS_TIMEOUT_DISABLED) return false
|
60
|
-
|
61
64
|
const { logger } = options
|
62
65
|
|
63
66
|
try {
|
64
67
|
if (isRegistered) {
|
65
|
-
|
68
|
+
logger.info('Timeout handler already registered')
|
69
|
+
return false
|
66
70
|
}
|
67
71
|
timeoutJob = CronJob.from({
|
68
72
|
start: false,
|
@@ -70,6 +74,7 @@ const register = async (options) => {
|
|
70
74
|
cronTime: Config.HANDLERS_TIMEOUT_TIMEXP,
|
71
75
|
timeZone: Config.HANDLERS_TIMEOUT_TIMEZONE
|
72
76
|
})
|
77
|
+
distLock = createDistLock(Config.HANDLERS_TIMEOUT?.DIST_LOCK, logger)
|
73
78
|
timeoutJob.start()
|
74
79
|
isRegistered = true
|
75
80
|
logger.info('Timeout handler registered')
|
@@ -83,7 +88,8 @@ const register = async (options) => {
|
|
83
88
|
const stop = async () => {
|
84
89
|
if (isRegistered) {
|
85
90
|
await timeoutJob.stop()
|
86
|
-
|
91
|
+
// await distLock?.releaseLock()
|
92
|
+
isRegistered = false
|
87
93
|
}
|
88
94
|
}
|
89
95
|
|
package/src/handlers/register.js
CHANGED
@@ -70,7 +70,7 @@ const registerHandlers = async (handlers, options) => {
|
|
70
70
|
const registerAllHandlers = async (options) => {
|
71
71
|
options.logger.debug('Registering all handlers')
|
72
72
|
await init(options)
|
73
|
-
TimeoutHandler.register(options)
|
73
|
+
await TimeoutHandler.register(options)
|
74
74
|
}
|
75
75
|
|
76
76
|
const stopAllHandlers = async (options) => {
|
package/src/lib/config.js
CHANGED
@@ -162,6 +162,7 @@ const config = {
|
|
162
162
|
HANDLERS_TIMEOUT_TIMEXP: RC.HANDLERS.TIMEOUT.TIMEXP,
|
163
163
|
HANDLERS_TIMEOUT_TIMEZONE: RC.HANDLERS.TIMEOUT.TIMEZONE,
|
164
164
|
HANDLERS_TIMEOUT_BATCH_SIZE: RC.HANDLERS.TIMEOUT.BATCH_SIZE,
|
165
|
+
HANDLERS_TIMEOUT_DIST_LOCK_ENABLED: RC.HANDLERS.TIMEOUT?.DIST_LOCK?.enabled,
|
165
166
|
ERROR_HANDLING: RC.ERROR_HANDLING,
|
166
167
|
SWITCH_ENDPOINT: RC.SWITCH_ENDPOINT,
|
167
168
|
INSTRUMENTATION_METRICS_DISABLED: RC.INSTRUMENTATION.METRICS.DISABLED,
|
@@ -0,0 +1,74 @@
|
|
1
|
+
/*****
|
2
|
+
License
|
3
|
+
--------------
|
4
|
+
Copyright © 2020-2025 Mojaloop Foundation
|
5
|
+
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
10
|
+
|
11
|
+
Contributors
|
12
|
+
--------------
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
15
|
+
should be listed with a '*' in the first column. People who have
|
16
|
+
contributed from an organization can be listed under the organization
|
17
|
+
that actually holds the copyright for their contributions (see the
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
20
|
+
optionally within square brackets <email>.
|
21
|
+
|
22
|
+
* Mojaloop Foundation
|
23
|
+
* Eugen Klymniuk <eugen.klymniuk@infitx.com>
|
24
|
+
|
25
|
+
--------------
|
26
|
+
******/
|
27
|
+
|
28
|
+
const { distLock } = require('@mojaloop/central-services-shared').Util
|
29
|
+
const { TIMEOUT_HANDLER_DIST_LOCK_KEY } = require('../constants')
|
30
|
+
|
31
|
+
const createDistLock = (distLockConfig, logger) => {
|
32
|
+
const distLockKey = TIMEOUT_HANDLER_DIST_LOCK_KEY
|
33
|
+
const distLockTtl = distLockConfig?.lockTimeout || 10000
|
34
|
+
const distLockAcquireTimeout = distLockConfig?.acquireTimeout || 5000
|
35
|
+
|
36
|
+
const lock = distLockConfig?.enabled
|
37
|
+
? distLock.createLock(distLockConfig, logger)
|
38
|
+
: null
|
39
|
+
|
40
|
+
const acquireLock = async () => {
|
41
|
+
if (lock) {
|
42
|
+
try {
|
43
|
+
return !!(await lock.acquire(distLockKey, distLockTtl, distLockAcquireTimeout))
|
44
|
+
} catch (err) {
|
45
|
+
logger.error('error acquiring distributed lock:', err)
|
46
|
+
// should this be added to metrics?
|
47
|
+
return false
|
48
|
+
}
|
49
|
+
}
|
50
|
+
logger.info('distributed lock not configured or disabled, running without distributed lock')
|
51
|
+
return true
|
52
|
+
}
|
53
|
+
|
54
|
+
const releaseLock = async () => {
|
55
|
+
if (lock) {
|
56
|
+
try {
|
57
|
+
await lock.release()
|
58
|
+
logger.verbose('distributed lock released')
|
59
|
+
} catch (error) {
|
60
|
+
logger.error('error releasing distributed lock:', error)
|
61
|
+
// should this be added to metrics?
|
62
|
+
}
|
63
|
+
} else {
|
64
|
+
logger.verbose('distributed lock not configured or disabled')
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
return {
|
69
|
+
acquireLock,
|
70
|
+
releaseLock
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
module.exports = createDistLock
|
package/src/lib/index.js
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
const { loggerFactory, asyncStorage } = require('@mojaloop/central-services-logger/src/contextLogger')
|
2
2
|
const { TransformFacades } = require('@mojaloop/ml-schema-transformer-lib')
|
3
|
+
const createDistLock = require('./createDistLock')
|
3
4
|
|
4
5
|
const logger = loggerFactory('ALS') // global logger without context
|
5
6
|
|
6
7
|
module.exports = {
|
7
8
|
logger,
|
8
9
|
asyncStorage,
|
9
|
-
TransformFacades
|
10
|
+
TransformFacades,
|
11
|
+
createDistLock
|
10
12
|
}
|
@@ -31,11 +31,23 @@
|
|
31
31
|
|
32
32
|
'use strict'
|
33
33
|
|
34
|
+
let mockDistLock
|
35
|
+
jest.mock('@mojaloop/central-services-shared', () => ({
|
36
|
+
...jest.requireActual('@mojaloop/central-services-shared'),
|
37
|
+
Util: {
|
38
|
+
...jest.requireActual('@mojaloop/central-services-shared').Util,
|
39
|
+
distLock: {
|
40
|
+
createLock: jest.fn().mockImplementation(() => mockDistLock)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}))
|
44
|
+
|
34
45
|
const CronJob = require('cron').CronJob
|
35
46
|
const TimeoutHandler = require('../../../src/handlers/TimeoutHandler')
|
36
47
|
const TimeoutService = require('../../../src/domain/timeout')
|
37
48
|
const Config = require('../../../src/lib/config')
|
38
49
|
const { logger } = require('../../../src/lib')
|
50
|
+
|
39
51
|
const DefaultConfig = { ...Config }
|
40
52
|
|
41
53
|
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
@@ -48,8 +60,19 @@ describe('TimeoutHandler', () => {
|
|
48
60
|
start: jest.fn(),
|
49
61
|
stop: jest.fn()
|
50
62
|
})
|
51
|
-
const mockProxyCache = {
|
52
|
-
|
63
|
+
const mockProxyCache = {
|
64
|
+
processExpiredAlsKeys: jest.fn(),
|
65
|
+
processExpiredProxyGetPartiesKeys: jest.fn()
|
66
|
+
}
|
67
|
+
mockOptions = {
|
68
|
+
proxyCache: mockProxyCache,
|
69
|
+
batchSize: 10,
|
70
|
+
logger
|
71
|
+
}
|
72
|
+
mockDistLock = {
|
73
|
+
acquire: jest.fn().mockResolvedValue(true),
|
74
|
+
release: jest.fn().mockResolvedValue()
|
75
|
+
}
|
53
76
|
})
|
54
77
|
|
55
78
|
afterEach(async () => {
|
@@ -57,13 +80,20 @@ describe('TimeoutHandler', () => {
|
|
57
80
|
})
|
58
81
|
|
59
82
|
describe('timeout', () => {
|
83
|
+
afterEach(async () => {
|
84
|
+
await TimeoutHandler.stop()
|
85
|
+
})
|
86
|
+
|
60
87
|
it('should execute timout service', async () => {
|
88
|
+
await TimeoutHandler.register(mockOptions)
|
61
89
|
jest.spyOn(TimeoutService, 'timeoutInterschemePartiesLookups').mockResolvedValue()
|
62
|
-
await
|
90
|
+
await TimeoutHandler.timeout(mockOptions)
|
63
91
|
expect(TimeoutService.timeoutInterschemePartiesLookups).toHaveBeenCalled()
|
64
92
|
})
|
65
93
|
|
66
|
-
it('should not run if isRunning is true', async () => {
|
94
|
+
it('should not run if isRunning is true (distLock disabled)', async () => {
|
95
|
+
await TimeoutHandler.register(mockOptions)
|
96
|
+
Config.HANDLERS_TIMEOUT_DIST_LOCK_ENABLED = false
|
67
97
|
jest.spyOn(TimeoutService, 'timeoutInterschemePartiesLookups').mockImplementation(async () => {
|
68
98
|
await wait(1000)
|
69
99
|
})
|
@@ -73,6 +103,30 @@ describe('TimeoutHandler', () => {
|
|
73
103
|
])
|
74
104
|
expect(TimeoutService.timeoutInterschemePartiesLookups).toHaveBeenCalledTimes(1)
|
75
105
|
})
|
106
|
+
|
107
|
+
it('should use distributed lock when enabled', async () => {
|
108
|
+
Config.HANDLERS_TIMEOUT.DIST_LOCK = { enabled: true }
|
109
|
+
jest.spyOn(TimeoutService, 'timeoutProxyGetPartiesLookups').mockResolvedValue()
|
110
|
+
await TimeoutHandler.register(mockOptions)
|
111
|
+
|
112
|
+
await TimeoutHandler.timeout(mockOptions)
|
113
|
+
expect(mockDistLock.acquire).toHaveBeenCalledTimes(1)
|
114
|
+
expect(mockDistLock.release).toHaveBeenCalledTimes(1)
|
115
|
+
expect(TimeoutService.timeoutProxyGetPartiesLookups).toHaveBeenCalled()
|
116
|
+
})
|
117
|
+
|
118
|
+
it('should not run if distributed lock cannot be acquired', async () => {
|
119
|
+
mockDistLock.acquire = jest.fn().mockResolvedValue(false)
|
120
|
+
Config.HANDLERS_TIMEOUT.DIST_LOCK = { enabled: true }
|
121
|
+
jest.spyOn(TimeoutService, 'timeoutProxyGetPartiesLookups').mockResolvedValue()
|
122
|
+
await TimeoutHandler.register(mockOptions)
|
123
|
+
|
124
|
+
await TimeoutHandler.timeout(mockOptions)
|
125
|
+
|
126
|
+
expect(mockDistLock.acquire).toHaveBeenCalledTimes(1)
|
127
|
+
expect(mockDistLock.release).toHaveBeenCalled() // todo: think if we need .release() to have been called
|
128
|
+
expect(TimeoutService.timeoutProxyGetPartiesLookups).not.toHaveBeenCalled()
|
129
|
+
})
|
76
130
|
})
|
77
131
|
|
78
132
|
describe('register', () => {
|