@onlineapps/infrastructure-tools 1.0.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/README.md +133 -0
- package/package.json +31 -0
- package/src/health/healthPublisher.js +209 -0
- package/src/index.js +31 -0
- package/src/orchestration/initInfrastructureQueues.js +145 -0
- package/src/orchestration/waitForInfrastructureReady.js +125 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# @onlineapps/infrastructure-tools
|
|
2
|
+
|
|
3
|
+
Infrastructure orchestration utilities for OA Drive infrastructure services.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This library provides utilities for infrastructure services to coordinate their initialization, health tracking, and queue management. It is **NOT** intended for business services.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @onlineapps/infrastructure-tools
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Health Publisher
|
|
18
|
+
|
|
19
|
+
Publish health checks to `infrastructure.health.checks` queue:
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const { createBaseClientAdapter } = require('@onlineapps/infrastructure-tools');
|
|
23
|
+
const BaseClient = require('@onlineapps/mq-client-core');
|
|
24
|
+
|
|
25
|
+
const mqClient = new BaseClient({ ... });
|
|
26
|
+
await mqClient.connect();
|
|
27
|
+
|
|
28
|
+
const healthPublisher = createBaseClientAdapter(
|
|
29
|
+
mqClient,
|
|
30
|
+
'gateway',
|
|
31
|
+
() => ({
|
|
32
|
+
mq: mqClient.isConnected() ? 'healthy' : 'unhealthy',
|
|
33
|
+
redis: 'healthy',
|
|
34
|
+
http: 'healthy'
|
|
35
|
+
}),
|
|
36
|
+
config.infrastructureHealth,
|
|
37
|
+
logger
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
await healthPublisher.start();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Wait for Infrastructure Ready
|
|
44
|
+
|
|
45
|
+
Wait for all infrastructure services to be ready before creating queues:
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
const { waitForInfrastructureReady } = require('@onlineapps/infrastructure-tools');
|
|
49
|
+
|
|
50
|
+
await waitForInfrastructureReady({
|
|
51
|
+
redisUrl: 'redis://api_node_cache:6379',
|
|
52
|
+
maxWait: 300000, // 5 minutes
|
|
53
|
+
checkInterval: 5000, // 5 seconds
|
|
54
|
+
logger: logger
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Initialize Infrastructure Queues
|
|
59
|
+
|
|
60
|
+
Initialize infrastructure queues with correct parameters:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const { initInfrastructureQueues } = require('@onlineapps/infrastructure-tools');
|
|
64
|
+
|
|
65
|
+
await initInfrastructureQueues(channel, {
|
|
66
|
+
queues: ['workflow.init'], // Only create specific queues
|
|
67
|
+
connection: connection,
|
|
68
|
+
logger: logger
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## API
|
|
73
|
+
|
|
74
|
+
### `waitForInfrastructureReady(options)`
|
|
75
|
+
|
|
76
|
+
Waits for all infrastructure services to be reported as healthy by Registry.
|
|
77
|
+
|
|
78
|
+
**Options:**
|
|
79
|
+
- `redisUrl` (string): Redis URL (default: from ENV or `redis://api_node_cache:6379`)
|
|
80
|
+
- `maxWait` (number): Maximum wait time in ms (default: 300000 = 5 minutes)
|
|
81
|
+
- `checkInterval` (number): Check interval in ms (default: 5000 = 5 seconds)
|
|
82
|
+
- `logger` (Object): Logger instance (default: console)
|
|
83
|
+
|
|
84
|
+
**Returns:** `Promise<boolean>`
|
|
85
|
+
|
|
86
|
+
### `initInfrastructureQueues(channel, options)`
|
|
87
|
+
|
|
88
|
+
Initializes infrastructure queues with correct parameters from `queueConfig`.
|
|
89
|
+
|
|
90
|
+
**Options:**
|
|
91
|
+
- `queues` (Array<string>): Specific queues to create (default: all infrastructure queues)
|
|
92
|
+
- `connection` (Object): RabbitMQ connection (for channel recreation)
|
|
93
|
+
- `logger` (Object): Logger instance (default: console)
|
|
94
|
+
- `queueConfig` (Object): Queue config instance (default: from mq-client-core)
|
|
95
|
+
|
|
96
|
+
**Returns:** `Promise<void>`
|
|
97
|
+
|
|
98
|
+
### `createHealthPublisher(options)`
|
|
99
|
+
|
|
100
|
+
Creates a health publisher instance with custom publish function.
|
|
101
|
+
|
|
102
|
+
**Options:**
|
|
103
|
+
- `serviceName` (string): Service name (required)
|
|
104
|
+
- `version` (string): Service version (default: '1.0.0')
|
|
105
|
+
- `publishFunction` (Function): Function to publish message (required)
|
|
106
|
+
- `getHealthData` (Function): Function to get health data (required)
|
|
107
|
+
- `config` (Object): Configuration with `infrastructureHealth` settings
|
|
108
|
+
- `logger` (Object): Logger instance (default: console)
|
|
109
|
+
|
|
110
|
+
**Returns:** Health publisher instance with `start()`, `stop()`, `publishNow()` methods
|
|
111
|
+
|
|
112
|
+
### `createBaseClientAdapter(baseClient, serviceName, getHealthData, config, logger)`
|
|
113
|
+
|
|
114
|
+
Creates health publisher adapter for BaseClient (from mq-client-core).
|
|
115
|
+
|
|
116
|
+
### `createAmqplibAdapter(connection, channel, serviceName, getHealthData, config, logger)`
|
|
117
|
+
|
|
118
|
+
Creates health publisher adapter for amqplib (direct connection + channel).
|
|
119
|
+
|
|
120
|
+
## Dependencies
|
|
121
|
+
|
|
122
|
+
- `@onlineapps/mq-client-core` - For queue configuration
|
|
123
|
+
- `redis` - For health status checking
|
|
124
|
+
|
|
125
|
+
## Related Libraries
|
|
126
|
+
|
|
127
|
+
- `@onlineapps/mq-client-core` - Core MQ operations and queue configuration
|
|
128
|
+
- `@onlineapps/monitoring-core` - Business monitoring (traces, metrics, logs)
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|
|
133
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@onlineapps/infrastructure-tools",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Infrastructure orchestration utilities for OA Drive infrastructure services (health tracking, queue initialization, service discovery)",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest",
|
|
8
|
+
"test:unit": "jest --testPathPattern=tests/unit",
|
|
9
|
+
"test:component": "jest --testPathPattern=tests/component",
|
|
10
|
+
"test:integration": "jest --testPathPattern=tests/integration"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"infrastructure",
|
|
14
|
+
"orchestration",
|
|
15
|
+
"health-tracking",
|
|
16
|
+
"service-discovery"
|
|
17
|
+
],
|
|
18
|
+
"author": "OnlineApps",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@onlineapps/mq-client-core": "^1.0.24",
|
|
22
|
+
"redis": "^4.6.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"jest": "^29.7.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Infrastructure Health Publisher
|
|
5
|
+
*
|
|
6
|
+
* Publishes health check messages to infrastructure.health.checks queue.
|
|
7
|
+
* Used by infrastructure services to report their health status to Registry.
|
|
8
|
+
*
|
|
9
|
+
* Supports multiple MQ abstractions via adapter pattern:
|
|
10
|
+
* - BaseClient (from mq-client-core)
|
|
11
|
+
* - amqplib directly (connection + channel)
|
|
12
|
+
* - Custom publish function
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create infrastructure health publisher
|
|
17
|
+
* @param {Object} options - Configuration options
|
|
18
|
+
* @param {string} options.serviceName - Service name (e.g., 'gateway', 'registry', 'validator')
|
|
19
|
+
* @param {string} [options.version] - Service version (default: '1.0.0')
|
|
20
|
+
* @param {Function} options.publishFunction - Function to publish message: async (queueName, data) => void
|
|
21
|
+
* @param {Function} options.getHealthData - Function to get current health status: () => Object
|
|
22
|
+
* @param {Object} [options.config] - Configuration object with infrastructureHealth settings
|
|
23
|
+
* @param {string} [options.config.queueName] - Queue name (default: 'infrastructure.health.checks')
|
|
24
|
+
* @param {number} [options.config.publishInterval] - Publish interval in ms (default: 5000)
|
|
25
|
+
* @param {Object} [options.logger] - Logger instance (default: console)
|
|
26
|
+
* @returns {Object} Health publisher instance
|
|
27
|
+
*/
|
|
28
|
+
function createHealthPublisher(options) {
|
|
29
|
+
const {
|
|
30
|
+
serviceName,
|
|
31
|
+
version = '1.0.0',
|
|
32
|
+
publishFunction,
|
|
33
|
+
getHealthData,
|
|
34
|
+
config = {},
|
|
35
|
+
logger = console
|
|
36
|
+
} = options;
|
|
37
|
+
|
|
38
|
+
if (!serviceName) {
|
|
39
|
+
throw new Error('serviceName is required');
|
|
40
|
+
}
|
|
41
|
+
if (typeof publishFunction !== 'function') {
|
|
42
|
+
throw new Error('publishFunction must be a function');
|
|
43
|
+
}
|
|
44
|
+
if (typeof getHealthData !== 'function') {
|
|
45
|
+
throw new Error('getHealthData must be a function');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let healthCheckInterval = null;
|
|
49
|
+
let isPublishing = false;
|
|
50
|
+
|
|
51
|
+
const queueName = config.queueName || config.infrastructureHealth?.queueName || process.env.INFRASTRUCTURE_HEALTH_QUEUE || 'infrastructure.health.checks';
|
|
52
|
+
const publishInterval = config.publishInterval || config.infrastructureHealth?.publishInterval || parseInt(process.env.INFRASTRUCTURE_HEALTH_PUBLISH_INTERVAL) || 5000;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Publish health check to infrastructure.health.checks queue
|
|
56
|
+
* @returns {Promise<void>}
|
|
57
|
+
*/
|
|
58
|
+
async function publishHealthCheck() {
|
|
59
|
+
if (isPublishing) {
|
|
60
|
+
logger.warn(`[InfrastructureHealth:${serviceName}] Health check already in progress, skipping`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
isPublishing = true;
|
|
66
|
+
const healthData = {
|
|
67
|
+
serviceName,
|
|
68
|
+
version,
|
|
69
|
+
status: 'UP',
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
components: getHealthData()
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
await publishFunction(queueName, healthData);
|
|
75
|
+
|
|
76
|
+
logger.debug(`[InfrastructureHealth:${serviceName}] Published health check`, {
|
|
77
|
+
queue: queueName,
|
|
78
|
+
status: healthData.status
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger.error(`[InfrastructureHealth:${serviceName}] Failed to publish health check`, {
|
|
82
|
+
error: error.message,
|
|
83
|
+
stack: error.stack
|
|
84
|
+
});
|
|
85
|
+
} finally {
|
|
86
|
+
isPublishing = false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Start publishing health checks periodically
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
async function start() {
|
|
95
|
+
if (healthCheckInterval) {
|
|
96
|
+
logger.warn(`[InfrastructureHealth:${serviceName}] Health check publisher already started`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Publish initial health check immediately
|
|
101
|
+
await publishHealthCheck();
|
|
102
|
+
|
|
103
|
+
// Then publish periodically
|
|
104
|
+
healthCheckInterval = setInterval(() => {
|
|
105
|
+
publishHealthCheck().catch(err => {
|
|
106
|
+
logger.error(`[InfrastructureHealth:${serviceName}] Error in health check publisher interval`, {
|
|
107
|
+
error: err.message
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}, publishInterval);
|
|
111
|
+
|
|
112
|
+
logger.info(`[InfrastructureHealth:${serviceName}] Health check publisher started`, {
|
|
113
|
+
interval: publishInterval,
|
|
114
|
+
queue: queueName
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Stop publishing health checks
|
|
120
|
+
*/
|
|
121
|
+
function stop() {
|
|
122
|
+
if (healthCheckInterval) {
|
|
123
|
+
clearInterval(healthCheckInterval);
|
|
124
|
+
healthCheckInterval = null;
|
|
125
|
+
logger.info(`[InfrastructureHealth:${serviceName}] Health check publisher stopped`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Publish health check immediately (manual trigger)
|
|
131
|
+
* @returns {Promise<void>}
|
|
132
|
+
*/
|
|
133
|
+
async function publishNow() {
|
|
134
|
+
await publishHealthCheck();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
start,
|
|
139
|
+
stop,
|
|
140
|
+
publishNow,
|
|
141
|
+
isRunning: () => healthCheckInterval !== null
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create health publisher adapter for BaseClient (from mq-client-core)
|
|
147
|
+
* @param {Object} baseClient - BaseClient instance
|
|
148
|
+
* @param {string} serviceName - Service name
|
|
149
|
+
* @param {Function} getHealthData - Function to get health data
|
|
150
|
+
* @param {Object} config - Configuration
|
|
151
|
+
* @param {Object} logger - Logger instance
|
|
152
|
+
* @returns {Object} Health publisher instance
|
|
153
|
+
*/
|
|
154
|
+
function createBaseClientAdapter(baseClient, serviceName, getHealthData, config, logger) {
|
|
155
|
+
const publishFunction = async (queueName, data) => {
|
|
156
|
+
if (!baseClient || typeof baseClient.publish !== 'function') {
|
|
157
|
+
throw new Error('BaseClient instance must have publish() method');
|
|
158
|
+
}
|
|
159
|
+
await baseClient.publish(queueName, data);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return createHealthPublisher({
|
|
163
|
+
serviceName,
|
|
164
|
+
publishFunction,
|
|
165
|
+
getHealthData,
|
|
166
|
+
config,
|
|
167
|
+
logger
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create health publisher adapter for amqplib (connection + channel)
|
|
173
|
+
* @param {Object} connection - AMQP connection
|
|
174
|
+
* @param {Object} channel - AMQP channel
|
|
175
|
+
* @param {string} serviceName - Service name
|
|
176
|
+
* @param {Function} getHealthData - Function to get health data
|
|
177
|
+
* @param {Object} config - Configuration
|
|
178
|
+
* @param {Object} logger - Logger instance
|
|
179
|
+
* @returns {Object} Health publisher instance
|
|
180
|
+
*/
|
|
181
|
+
function createAmqplibAdapter(connection, channel, serviceName, getHealthData, config, logger) {
|
|
182
|
+
const publishFunction = async (queueName, data) => {
|
|
183
|
+
if (!channel || channel.closed) {
|
|
184
|
+
throw new Error('AMQP channel is not available or closed');
|
|
185
|
+
}
|
|
186
|
+
// Ensure queue exists (it should be created by Registry, but assert just in case)
|
|
187
|
+
await channel.assertQueue(queueName, { durable: true });
|
|
188
|
+
await channel.sendToQueue(
|
|
189
|
+
queueName,
|
|
190
|
+
Buffer.from(JSON.stringify(data)),
|
|
191
|
+
{ persistent: true }
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return createHealthPublisher({
|
|
196
|
+
serviceName,
|
|
197
|
+
publishFunction,
|
|
198
|
+
getHealthData,
|
|
199
|
+
config,
|
|
200
|
+
logger
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
createHealthPublisher,
|
|
206
|
+
createBaseClientAdapter,
|
|
207
|
+
createAmqplibAdapter
|
|
208
|
+
};
|
|
209
|
+
|
package/src/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @onlineapps/infrastructure-tools
|
|
5
|
+
*
|
|
6
|
+
* Infrastructure orchestration utilities for OA Drive infrastructure services.
|
|
7
|
+
* Provides health tracking, queue initialization, and service discovery utilities.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: This library is for infrastructure services only.
|
|
10
|
+
* Business services should NOT use this library.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { waitForInfrastructureReady } = require('./orchestration/waitForInfrastructureReady');
|
|
14
|
+
const { initInfrastructureQueues } = require('./orchestration/initInfrastructureQueues');
|
|
15
|
+
const {
|
|
16
|
+
createHealthPublisher,
|
|
17
|
+
createBaseClientAdapter,
|
|
18
|
+
createAmqplibAdapter
|
|
19
|
+
} = require('./health/healthPublisher');
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
// Orchestration utilities
|
|
23
|
+
waitForInfrastructureReady,
|
|
24
|
+
initInfrastructureQueues,
|
|
25
|
+
|
|
26
|
+
// Health tracking utilities
|
|
27
|
+
createHealthPublisher,
|
|
28
|
+
createBaseClientAdapter,
|
|
29
|
+
createAmqplibAdapter
|
|
30
|
+
};
|
|
31
|
+
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* initInfrastructureQueues.js
|
|
5
|
+
*
|
|
6
|
+
* Utility for infrastructure services to initialize infrastructure queues.
|
|
7
|
+
* Uses queueConfig from @onlineapps/mq-client-core for configuration.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: This is for infrastructure services (gateway, monitoring, etc.)
|
|
10
|
+
* Business services should use @onlineapps/conn-infra-mq connector instead.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize all infrastructure queues
|
|
15
|
+
* @param {Object} channel - RabbitMQ channel
|
|
16
|
+
* @param {Object} options - Options
|
|
17
|
+
* @param {Array<string>} [options.queues] - Specific queues to create (default: all infrastructure queues)
|
|
18
|
+
* @param {Object} [options.logger] - Logger instance (default: console)
|
|
19
|
+
* @param {Object} [options.connection] - RabbitMQ connection (for channel recreation)
|
|
20
|
+
* @param {Object} [options.queueConfig] - Queue config instance (default: from mq-client-core)
|
|
21
|
+
* @returns {Promise<void>}
|
|
22
|
+
*/
|
|
23
|
+
async function initInfrastructureQueues(channel, options = {}) {
|
|
24
|
+
if (!channel) {
|
|
25
|
+
throw new Error('initInfrastructureQueues requires a valid channel');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const logger = options.logger || console;
|
|
29
|
+
const connection = options.connection || channel.connection;
|
|
30
|
+
|
|
31
|
+
// Load queueConfig from mq-client-core
|
|
32
|
+
// NOTE: queueConfig is part of mq-client-core because it's used by both:
|
|
33
|
+
// - Infrastructure services (via infrastructure-tools)
|
|
34
|
+
// - Business services (via conn-infra-mq connector)
|
|
35
|
+
const queueConfig = options.queueConfig || require('@onlineapps/mq-client-core/src/config/queueConfig');
|
|
36
|
+
|
|
37
|
+
const queuesToCreate = options.queues || [
|
|
38
|
+
// Workflow infrastructure queues
|
|
39
|
+
'workflow.init',
|
|
40
|
+
'workflow.completed',
|
|
41
|
+
'workflow.failed',
|
|
42
|
+
'workflow.dlq',
|
|
43
|
+
// Registry infrastructure queues
|
|
44
|
+
'registry.register',
|
|
45
|
+
'registry.heartbeats'
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const watchChannel = (ch) => {
|
|
49
|
+
if (!ch) return ch;
|
|
50
|
+
if (!ch.__queueInitLinked) {
|
|
51
|
+
ch.__queueInitLinked = true;
|
|
52
|
+
ch.once('close', () => {
|
|
53
|
+
ch.__queueInitClosed = true;
|
|
54
|
+
logger.warn('[QueueInit] Queue channel closed');
|
|
55
|
+
});
|
|
56
|
+
ch.on('error', (err) => {
|
|
57
|
+
ch.__queueInitClosed = true;
|
|
58
|
+
logger.warn(`[QueueInit] Queue channel error: ${err.message}`);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return ch;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
let workingChannel = watchChannel(channel);
|
|
65
|
+
|
|
66
|
+
const getChannel = async (forceNew = false) => {
|
|
67
|
+
if (!forceNew && workingChannel && !workingChannel.__queueInitClosed) {
|
|
68
|
+
return workingChannel;
|
|
69
|
+
}
|
|
70
|
+
if (!connection) {
|
|
71
|
+
throw new Error('Queue channel closed and no connection reference available to recreate it');
|
|
72
|
+
}
|
|
73
|
+
workingChannel = watchChannel(await connection.createChannel());
|
|
74
|
+
return workingChannel;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
logger.log(`[QueueInit] Initializing ${queuesToCreate.length} infrastructure queues...`);
|
|
78
|
+
|
|
79
|
+
for (const queueName of queuesToCreate) {
|
|
80
|
+
try {
|
|
81
|
+
// Verify it's an infrastructure queue
|
|
82
|
+
if (!queueConfig.isInfrastructureQueue(queueName)) {
|
|
83
|
+
logger.warn(`[QueueInit] Skipping ${queueName} - not an infrastructure queue`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Get unified configuration
|
|
88
|
+
const config = queueConfig.getInfrastructureQueueConfig(queueName);
|
|
89
|
+
|
|
90
|
+
// CRITICAL: Check if queue exists with different arguments (406 error)
|
|
91
|
+
// If so, delete it and recreate with correct parameters
|
|
92
|
+
const ensureQueue = async () => {
|
|
93
|
+
const activeChannel = await getChannel();
|
|
94
|
+
return activeChannel.assertQueue(queueName, {
|
|
95
|
+
durable: config.durable !== false,
|
|
96
|
+
arguments: { ...config.arguments }
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await ensureQueue();
|
|
102
|
+
logger.log(`[QueueInit] ✓ Created/verified infrastructure queue: ${queueName}`);
|
|
103
|
+
} catch (assertError) {
|
|
104
|
+
if (assertError.code === 406) {
|
|
105
|
+
logger.warn(
|
|
106
|
+
`[QueueInit] ⚠ Queue ${queueName} exists with different arguments - deleting and recreating...`
|
|
107
|
+
);
|
|
108
|
+
try {
|
|
109
|
+
const deleteChannel = await getChannel(true);
|
|
110
|
+
await deleteChannel.deleteQueue(queueName);
|
|
111
|
+
logger.log(`[QueueInit] ✓ Deleted queue ${queueName} with incorrect parameters`);
|
|
112
|
+
} catch (deleteError) {
|
|
113
|
+
logger.error(
|
|
114
|
+
`[QueueInit] ✗ Failed to delete queue ${queueName}:`,
|
|
115
|
+
deleteError.message
|
|
116
|
+
);
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Cannot delete queue ${queueName} with incorrect parameters: ${deleteError.message}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Recreate with correct parameters (force fresh channel to avoid closed state)
|
|
123
|
+
await getChannel(true);
|
|
124
|
+
await ensureQueue();
|
|
125
|
+
logger.log(
|
|
126
|
+
`[QueueInit] ✓ Recreated infrastructure queue: ${queueName} with correct parameters`
|
|
127
|
+
);
|
|
128
|
+
} else {
|
|
129
|
+
logger.error(`[QueueInit] ✗ Failed to create ${queueName}:`, assertError.message);
|
|
130
|
+
throw assertError;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
logger.error(`[QueueInit] ✗ Failed to initialize queue ${queueName}:`, error.message);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
logger.log(`[QueueInit] Infrastructure queues initialization complete`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
initInfrastructureQueues
|
|
144
|
+
};
|
|
145
|
+
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* waitForInfrastructureReady.js
|
|
5
|
+
*
|
|
6
|
+
* Waits for all infrastructure services to be verified as running and healthy.
|
|
7
|
+
* Used by infrastructure services before creating queues to ensure system consistency.
|
|
8
|
+
*
|
|
9
|
+
* This prevents race conditions where queues are created before all services are ready.
|
|
10
|
+
*
|
|
11
|
+
* **How it works:**
|
|
12
|
+
* 1. Connects to Redis (where Registry stores infrastructure health status)
|
|
13
|
+
* 2. Checks `infrastructure:health:all` key every 5 seconds
|
|
14
|
+
* 3. If key is `"true"` → all infrastructure services are UP → return success
|
|
15
|
+
* 4. If key is `"false"` or missing → wait and retry
|
|
16
|
+
* 5. If timeout reached → throw error
|
|
17
|
+
*
|
|
18
|
+
* **Why Redis (not HTTP):**
|
|
19
|
+
* - Fast: In-memory lookup, no network overhead
|
|
20
|
+
* - Reliable: Redis is already required infrastructure
|
|
21
|
+
* - Low latency: Direct key lookup, no HTTP parsing
|
|
22
|
+
* - Consistent: Same data source as Registry uses
|
|
23
|
+
* - No single point of failure: Redis can be clustered
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Wait for all infrastructure services to be ready
|
|
28
|
+
* @param {Object} options - Options
|
|
29
|
+
* @param {string} [options.redisUrl] - Redis URL (default: REDIS_URL env or redis://api_node_cache:6379)
|
|
30
|
+
* @param {number} [options.maxWait] - Maximum wait time in ms (default: INFRASTRUCTURE_HEALTH_WAIT_MAX_TIME env or 300000 = 5 minutes)
|
|
31
|
+
* @param {number} [options.checkInterval] - Check interval in ms (default: INFRASTRUCTURE_HEALTH_WAIT_CHECK_INTERVAL env or 5000 = 5 seconds)
|
|
32
|
+
* @param {Object} [options.logger] - Logger instance (default: console)
|
|
33
|
+
* @returns {Promise<boolean>} - True if all infrastructure services are ready
|
|
34
|
+
* @throws {Error} - If timeout is reached
|
|
35
|
+
*
|
|
36
|
+
* Note: Default values can be overridden via ENV variables or options parameter.
|
|
37
|
+
* Registry config (config.infrastructureHealth) is the source of truth for these defaults.
|
|
38
|
+
*/
|
|
39
|
+
async function waitForInfrastructureReady(options = {}) {
|
|
40
|
+
const redisUrl = options.redisUrl || process.env.REDIS_URL || 'redis://api_node_cache:6379';
|
|
41
|
+
const maxWait = options.maxWait || parseInt(process.env.INFRASTRUCTURE_HEALTH_WAIT_MAX_TIME) || 300000; // 5 minutes
|
|
42
|
+
const checkInterval = options.checkInterval || parseInt(process.env.INFRASTRUCTURE_HEALTH_WAIT_CHECK_INTERVAL) || 5000; // 5 seconds
|
|
43
|
+
const logger = options.logger || console;
|
|
44
|
+
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
let attemptCount = 0;
|
|
47
|
+
let redis = null;
|
|
48
|
+
|
|
49
|
+
logger.log('[InfrastructureReady] Waiting for all infrastructure services to be ready...');
|
|
50
|
+
logger.log(`[InfrastructureReady] Redis URL: ${redisUrl}`);
|
|
51
|
+
logger.log(`[InfrastructureReady] Max wait: ${maxWait}ms, Check interval: ${checkInterval}ms`);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Connect to Redis
|
|
55
|
+
const { createClient } = require('redis');
|
|
56
|
+
redis = createClient({ url: redisUrl });
|
|
57
|
+
|
|
58
|
+
redis.on('error', (err) => {
|
|
59
|
+
logger.log(`[InfrastructureReady] Redis error: ${err.message}`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
await redis.connect();
|
|
63
|
+
logger.log('[InfrastructureReady] Connected to Redis');
|
|
64
|
+
|
|
65
|
+
while (Date.now() - startTime < maxWait) {
|
|
66
|
+
attemptCount++;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Check Redis key: infrastructure:health:all
|
|
70
|
+
const allHealthy = await redis.get('infrastructure:health:all');
|
|
71
|
+
|
|
72
|
+
if (allHealthy === 'true') {
|
|
73
|
+
// All services are UP, we can proceed
|
|
74
|
+
const elapsed = Date.now() - startTime;
|
|
75
|
+
logger.log(`[InfrastructureReady] ✓ All infrastructure services are ready (took ${elapsed}ms, ${attemptCount} attempts)`);
|
|
76
|
+
|
|
77
|
+
// Optionally log individual service status
|
|
78
|
+
// Note: Service names should match config.infrastructureServices in Registry
|
|
79
|
+
const serviceKeys = ['gateway', 'registry', 'validator', 'delivery', 'monitoring'];
|
|
80
|
+
const statuses = {};
|
|
81
|
+
for (const serviceName of serviceKeys) {
|
|
82
|
+
const status = await redis.get(`infrastructure:health:${serviceName}`);
|
|
83
|
+
if (status) {
|
|
84
|
+
statuses[serviceName] = JSON.parse(status);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
logger.log(`[InfrastructureReady] Infrastructure status:`, JSON.stringify(statuses, null, 2));
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Not all services ready yet
|
|
93
|
+
logger.log(`[InfrastructureReady] Attempt ${attemptCount}: Not all services ready (allHealthy: ${allHealthy || 'null'})`);
|
|
94
|
+
logger.log(`[InfrastructureReady] Waiting ${checkInterval}ms before next check...`);
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
// Redis might not be ready yet, or connection issue
|
|
98
|
+
logger.log(`[InfrastructureReady] Attempt ${attemptCount}: Redis check failed (${error.message})`);
|
|
99
|
+
logger.log(`[InfrastructureReady] Waiting ${checkInterval}ms before retry...`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Wait before next check
|
|
103
|
+
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Timeout reached
|
|
107
|
+
const elapsed = Date.now() - startTime;
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Infrastructure services not ready within ${maxWait}ms (${elapsed}ms elapsed, ${attemptCount} attempts). ` +
|
|
110
|
+
`Check Redis keys: infrastructure:health:*`
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
} finally {
|
|
114
|
+
// Clean up Redis connection
|
|
115
|
+
if (redis && redis.isReady) {
|
|
116
|
+
await redis.quit();
|
|
117
|
+
logger.log('[InfrastructureReady] Redis connection closed');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
waitForInfrastructureReady
|
|
124
|
+
};
|
|
125
|
+
|