kukuy 1.4.0 → 1.6.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/.env.ssl +22 -0
- package/README.md +107 -171
- package/balancer.log +2 -0
- package/certs/auto/certificate.crt +22 -0
- package/certs/auto/private.key +28 -0
- package/kukuy-plugins/README.md +91 -0
- package/kukuy-plugins/ejemplo-plugin/index.js +39 -0
- package/kukuy-plugins/ejemplo-plugin/manifest.json +11 -0
- package/kukuy.js +51 -5
- package/package.json +8 -2
- package/servers_real.json +5 -0
- package/src/algorithms/IPHashAlgorithm.js +25 -13
- package/src/algorithms/RoundRobinAlgorithm.js +25 -27
- package/src/core/Balancer.js +201 -128
- package/src/core/ServerPool.js +46 -5
- package/src/extensibility/ExtendedFilterChain.js +90 -0
- package/src/extensibility/ExtendedHookManager.js +87 -0
- package/src/extensibility/PostStartupExtension.js +97 -0
- package/src/plugins/PluginManager.js +183 -0
- package/src/utils/HealthChecker.js +11 -5
- package/src/utils/ProfessionalMetrics.js +41 -24
- package/start-ssl-config.sh +24 -0
- package/start-ssl.sh +26 -0
- package/webpage/index.html +1 -1
- package/.ctagsd/ctagsd.json +0 -954
- package/.ctagsd/file_list.txt +0 -100
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +0 -101
- package/LICENSE +0 -680
- package/captura.png +0 -0
- package/kukuy.workspace +0 -11
- package/restart-balancer.sh +0 -10
- package/scripts/load_test.py +0 -151
- package/stress-test.js +0 -190
package/src/core/ServerPool.js
CHANGED
|
@@ -3,8 +3,12 @@ const { HealthChecker } = require('../utils/HealthChecker');
|
|
|
3
3
|
class ServerPool {
|
|
4
4
|
constructor() {
|
|
5
5
|
this.servers = [];
|
|
6
|
+
this.healthyServers = []; // Caché de servidores saludables
|
|
7
|
+
this.serversById = new Map(); // Acceso rápido por ID
|
|
8
|
+
this.serversByTag = new Map(); // Caché de servidores por tag
|
|
6
9
|
this.healthChecker = new HealthChecker();
|
|
7
10
|
this.nextId = 1;
|
|
11
|
+
this.cacheValid = false; // Indicador de validez de caché
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
addServer(serverConfig) {
|
|
@@ -22,6 +26,12 @@ class ServerPool {
|
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
this.servers.push(server);
|
|
29
|
+
this.serversById.set(server.id, server);
|
|
30
|
+
|
|
31
|
+
// Actualizar cachés
|
|
32
|
+
this.updateTagCache(server);
|
|
33
|
+
this.cacheValid = false;
|
|
34
|
+
|
|
25
35
|
return server;
|
|
26
36
|
}
|
|
27
37
|
|
|
@@ -36,19 +46,39 @@ class ServerPool {
|
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
getHealthyServers() {
|
|
39
|
-
|
|
49
|
+
if (!this.cacheValid) {
|
|
50
|
+
this.healthyServers = this.servers.filter(server => server.healthy && server.active);
|
|
51
|
+
this.cacheValid = true;
|
|
52
|
+
}
|
|
53
|
+
return this.healthyServers;
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
getServersByTag(tag) {
|
|
43
|
-
|
|
57
|
+
if (this.serversByTag.has(tag)) {
|
|
58
|
+
return this.serversByTag.get(tag);
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Actualiza la caché de servidores por tag
|
|
64
|
+
updateTagCache(server) {
|
|
65
|
+
for (const tag of server.tags) {
|
|
66
|
+
if (!this.serversByTag.has(tag)) {
|
|
67
|
+
this.serversByTag.set(tag, []);
|
|
68
|
+
}
|
|
69
|
+
this.serversByTag.get(tag).push(server);
|
|
70
|
+
}
|
|
44
71
|
}
|
|
45
72
|
|
|
46
73
|
markServerAsFailed(serverId) {
|
|
47
|
-
const server = this.
|
|
74
|
+
const server = this.serversById.get(serverId);
|
|
48
75
|
if (server) {
|
|
49
76
|
server.failedAttempts++;
|
|
50
77
|
server.healthy = false;
|
|
51
|
-
|
|
78
|
+
|
|
79
|
+
// Invalidar caché porque un servidor cambió de estado
|
|
80
|
+
this.cacheValid = false;
|
|
81
|
+
|
|
52
82
|
// Programar verificación de salud después de un tiempo
|
|
53
83
|
setTimeout(() => {
|
|
54
84
|
this.healthChecker.checkServerHealth(server)
|
|
@@ -57,11 +87,17 @@ class ServerPool {
|
|
|
57
87
|
server.healthy = true;
|
|
58
88
|
server.failedAttempts = 0;
|
|
59
89
|
server.lastChecked = Date.now();
|
|
90
|
+
|
|
91
|
+
// Invalidar caché porque un servidor cambió de estado
|
|
92
|
+
this.cacheValid = false;
|
|
60
93
|
} else {
|
|
61
94
|
server.lastChecked = Date.now();
|
|
62
95
|
// Si ha fallado demasiadas veces, mantenerlo inactivo
|
|
63
96
|
if (server.failedAttempts > 5) {
|
|
64
97
|
server.active = false;
|
|
98
|
+
|
|
99
|
+
// Invalidar caché porque un servidor cambió de estado
|
|
100
|
+
this.cacheValid = false;
|
|
65
101
|
}
|
|
66
102
|
}
|
|
67
103
|
});
|
|
@@ -70,7 +106,12 @@ class ServerPool {
|
|
|
70
106
|
}
|
|
71
107
|
|
|
72
108
|
getServerById(serverId) {
|
|
73
|
-
return this.
|
|
109
|
+
return this.serversById.get(serverId);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Método para invalidar la caché manualmente si es necesario
|
|
113
|
+
invalidateCache() {
|
|
114
|
+
this.cacheValid = false;
|
|
74
115
|
}
|
|
75
116
|
}
|
|
76
117
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { FilterChain } = require('./FilterChain');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extended FilterChain that allows registering filters after startup
|
|
5
|
+
*/
|
|
6
|
+
class ExtendedFilterChain {
|
|
7
|
+
constructor() {
|
|
8
|
+
// Create a new instance of the original FilterChain
|
|
9
|
+
this.originalFilterChain = new FilterChain();
|
|
10
|
+
|
|
11
|
+
// Storage for post-startup filters
|
|
12
|
+
this.postStartupFilters = {
|
|
13
|
+
request_processing: [] // Using the same hook type as original
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register a filter after startup
|
|
19
|
+
* @param {string} type - Type of filter ('request_processing' for now)
|
|
20
|
+
* @param {Function} filterFunction - The filter function to register
|
|
21
|
+
* @param {number} priority - Priority of the filter (lower numbers execute first)
|
|
22
|
+
*/
|
|
23
|
+
registerFilter(type, filterFunction, priority = 0) {
|
|
24
|
+
if (!this.postStartupFilters[type]) {
|
|
25
|
+
this.postStartupFilters[type] = [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Insert filter according to priority
|
|
29
|
+
const filterObj = {
|
|
30
|
+
fn: filterFunction,
|
|
31
|
+
priority,
|
|
32
|
+
id: this.generateId()
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this.postStartupFilters[type].push(filterObj);
|
|
36
|
+
this.postStartupFilters[type].sort((a, b) => a.priority - b.priority);
|
|
37
|
+
|
|
38
|
+
// Also add to the original hook system
|
|
39
|
+
this.originalFilterChain.addFilter(type, filterFunction);
|
|
40
|
+
|
|
41
|
+
console.log(`Filter registered for ${type} with priority ${priority}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Unregister a specific filter
|
|
46
|
+
* @param {string} type - Type of filter
|
|
47
|
+
* @param {string} filterId - ID of the filter to remove
|
|
48
|
+
*/
|
|
49
|
+
unregisterFilter(type, filterId) {
|
|
50
|
+
if (this.postStartupFilters[type]) {
|
|
51
|
+
this.postStartupFilters[type] = this.postStartupFilters[type].filter(f => f.id !== filterId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Apply all filters of a specific type
|
|
57
|
+
* @param {string} type - Type of filter
|
|
58
|
+
* @param {Object} req - Request object
|
|
59
|
+
* @param {Object} res - Response object
|
|
60
|
+
* @param {Object} additionalData - Additional data to pass to filters
|
|
61
|
+
* @returns {Object} Result of filter processing
|
|
62
|
+
*/
|
|
63
|
+
async applyFilters(type, req, res, additionalData = {}) {
|
|
64
|
+
// Apply filters using the original system which now includes post-startup filters
|
|
65
|
+
return await this.originalFilterChain.applyFilters(type, req, res, additionalData);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generate a unique ID
|
|
70
|
+
* @returns {string} Unique identifier
|
|
71
|
+
*/
|
|
72
|
+
generateId() {
|
|
73
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Proxy other methods to the original FilterChain
|
|
77
|
+
addFilter(filterName, filterFunction) {
|
|
78
|
+
this.originalFilterChain.addFilter(filterName, filterFunction);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
applySpecificFilter(filterName, data) {
|
|
82
|
+
return this.originalFilterChain.applySpecificFilter(filterName, data);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getCacheStats() {
|
|
86
|
+
return this.originalFilterChain.getCacheStats();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { ExtendedFilterChain };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const { HookManager } = require('./HookManager');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extended HookManager that allows registering hooks after startup
|
|
5
|
+
*/
|
|
6
|
+
class ExtendedHookManager {
|
|
7
|
+
constructor() {
|
|
8
|
+
// Create a new instance of the original HookManager
|
|
9
|
+
this.originalHookManager = new HookManager();
|
|
10
|
+
|
|
11
|
+
// Storage for post-startup hooks
|
|
12
|
+
this.postStartupHooks = {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Register a hook after startup
|
|
17
|
+
* @param {string} hookName - Name of the hook
|
|
18
|
+
* @param {Function} callback - The callback function to register
|
|
19
|
+
* @param {number} priority - Priority of the hook (lower numbers execute first)
|
|
20
|
+
*/
|
|
21
|
+
registerHook(hookName, callback, priority = 0) {
|
|
22
|
+
if (!this.postStartupHooks[hookName]) {
|
|
23
|
+
this.postStartupHooks[hookName] = [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const hookObj = {
|
|
27
|
+
callback,
|
|
28
|
+
priority,
|
|
29
|
+
id: this.generateId()
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
this.postStartupHooks[hookName].push(hookObj);
|
|
33
|
+
this.postStartupHooks[hookName].sort((a, b) => a.priority - b.priority);
|
|
34
|
+
|
|
35
|
+
// Add to the original hook system
|
|
36
|
+
this.originalHookManager.addHook(hookName, callback);
|
|
37
|
+
|
|
38
|
+
console.log(`Hook registered: ${hookName} with priority ${priority}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Unregister a specific hook
|
|
43
|
+
* @param {string} hookName - Name of the hook
|
|
44
|
+
* @param {string} hookId - ID of the hook to remove
|
|
45
|
+
*/
|
|
46
|
+
unregisterHook(hookName, hookId) {
|
|
47
|
+
if (this.postStartupHooks[hookName]) {
|
|
48
|
+
this.postStartupHooks[hookName] = this.postStartupHooks[hookName].filter(h => h.id !== hookId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Execute all hooks for a specific event
|
|
54
|
+
* @param {string} hookName - Name of the hook
|
|
55
|
+
* @param {Object} data - Data to pass to the hooks
|
|
56
|
+
*/
|
|
57
|
+
async executeHooks(hookName, data) {
|
|
58
|
+
// Execute hooks using the original system which now includes post-startup hooks
|
|
59
|
+
return await this.originalHookManager.executeHooks(hookName, data);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Generate a unique ID
|
|
64
|
+
* @returns {string} Unique identifier
|
|
65
|
+
*/
|
|
66
|
+
generateId() {
|
|
67
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Proxy other methods to the original HookManager
|
|
71
|
+
addHook(hookName, callback) {
|
|
72
|
+
this.originalHookManager.addHook(hookName, callback);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
executeHooks(hookName, data) {
|
|
76
|
+
return this.originalHookManager.executeHooks(hookName, data);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getRegisteredHooks() {
|
|
80
|
+
return Object.keys(this.postStartupHooks).reduce((acc, hookName) => {
|
|
81
|
+
acc[hookName] = this.postStartupHooks[hookName].length;
|
|
82
|
+
return acc;
|
|
83
|
+
}, {});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { ExtendedHookManager };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API for registering extensions after Kukuy startup
|
|
3
|
+
*/
|
|
4
|
+
class PostStartupExtension {
|
|
5
|
+
constructor(balancer) {
|
|
6
|
+
this.balancer = balancer;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Register a filter after startup
|
|
11
|
+
* @param {string} type - Type of filter ('request_processing' for now)
|
|
12
|
+
* @param {Function} filterFunction - The filter function to register
|
|
13
|
+
* @param {number} priority - Priority of the filter (lower numbers execute first)
|
|
14
|
+
*/
|
|
15
|
+
registerFilter(type, filterFunction, priority = 0) {
|
|
16
|
+
if (this.balancer.filterChain && typeof this.balancer.filterChain.registerFilter === 'function') {
|
|
17
|
+
this.balancer.filterChain.registerFilter(type, filterFunction, priority);
|
|
18
|
+
} else {
|
|
19
|
+
console.error('FilterChain no disponible o registerFilter no es una función');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Unregister a filter
|
|
25
|
+
* @param {string} type - Type of filter
|
|
26
|
+
* @param {string} filterId - ID of the filter to remove
|
|
27
|
+
*/
|
|
28
|
+
unregisterFilter(type, filterId) {
|
|
29
|
+
if (this.balancer.filterChain && typeof this.balancer.filterChain.unregisterFilter === 'function') {
|
|
30
|
+
this.balancer.filterChain.unregisterFilter(type, filterId);
|
|
31
|
+
} else {
|
|
32
|
+
console.error('FilterChain no disponible o unregisterFilter no es una función');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Register a hook after startup
|
|
38
|
+
* @param {string} hookName - Name of the hook
|
|
39
|
+
* @param {Function} callback - The callback function to register
|
|
40
|
+
* @param {number} priority - Priority of the hook (lower numbers execute first)
|
|
41
|
+
*/
|
|
42
|
+
registerHook(hookName, callback, priority = 0) {
|
|
43
|
+
if (this.balancer.hookManager && typeof this.balancer.hookManager.registerHook === 'function') {
|
|
44
|
+
this.balancer.hookManager.registerHook(hookName, callback, priority);
|
|
45
|
+
} else {
|
|
46
|
+
console.error('HookManager no disponible o registerHook no es una función');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Unregister a hook
|
|
52
|
+
* @param {string} hookName - Name of the hook
|
|
53
|
+
* @param {string} hookId - ID of the hook to remove
|
|
54
|
+
*/
|
|
55
|
+
unregisterHook(hookName, hookId) {
|
|
56
|
+
if (this.balancer.hookManager && typeof this.balancer.hookManager.unregisterHook === 'function') {
|
|
57
|
+
this.balancer.hookManager.unregisterHook(hookName, hookId);
|
|
58
|
+
} else {
|
|
59
|
+
console.error('HookManager no disponible o unregisterHook no es una función');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get access to system components
|
|
65
|
+
* @param {string} componentName - Name of the component to access
|
|
66
|
+
* @returns {Object} The requested component or null
|
|
67
|
+
*/
|
|
68
|
+
getComponent(componentName) {
|
|
69
|
+
switch(componentName) {
|
|
70
|
+
case 'serverPool':
|
|
71
|
+
return this.balancer.serverPool;
|
|
72
|
+
case 'algorithmManager':
|
|
73
|
+
return this.balancer.algorithmManager;
|
|
74
|
+
case 'metricsCollector':
|
|
75
|
+
return this.balancer.metricsCollector;
|
|
76
|
+
case 'config':
|
|
77
|
+
return this.balancer.config;
|
|
78
|
+
case 'routeLoader':
|
|
79
|
+
return this.balancer.routeLoader;
|
|
80
|
+
default:
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get information about registered hooks
|
|
87
|
+
* @returns {Object} Information about registered hooks
|
|
88
|
+
*/
|
|
89
|
+
getRegisteredHooks() {
|
|
90
|
+
if (this.balancer.hookManager && typeof this.balancer.hookManager.getRegisteredHooks === 'function') {
|
|
91
|
+
return this.balancer.hookManager.getRegisteredHooks();
|
|
92
|
+
}
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { PostStartupExtension };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PluginInstance represents a loaded plugin instance
|
|
6
|
+
*/
|
|
7
|
+
class PluginInstance {
|
|
8
|
+
constructor(name, manifest, module, balancer) {
|
|
9
|
+
this.name = name;
|
|
10
|
+
this.manifest = manifest;
|
|
11
|
+
this.module = module;
|
|
12
|
+
this.balancer = balancer;
|
|
13
|
+
this.isActive = false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async activate() {
|
|
17
|
+
if (typeof this.module.init === 'function') {
|
|
18
|
+
await this.module.init(this.balancer);
|
|
19
|
+
}
|
|
20
|
+
this.isActive = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async deactivate() {
|
|
24
|
+
if (typeof this.module.deinit === 'function') {
|
|
25
|
+
await this.module.deinit(this.balancer);
|
|
26
|
+
}
|
|
27
|
+
this.isActive = false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* PluginManager handles the discovery and loading of plugins
|
|
33
|
+
*/
|
|
34
|
+
class PluginManager {
|
|
35
|
+
constructor(balancer) {
|
|
36
|
+
this.balancer = balancer;
|
|
37
|
+
this.plugins = new Map();
|
|
38
|
+
this.pluginPath = process.env.PLUGINS_DIR || './kukuy-plugins';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if a path is a directory
|
|
43
|
+
* @param {string} filePath - Path to check
|
|
44
|
+
* @returns {boolean} - True if path is a directory
|
|
45
|
+
*/
|
|
46
|
+
isDirectory(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
const stat = fs.statSync(filePath);
|
|
49
|
+
return stat.isDirectory();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Load all plugins from the plugins directory
|
|
57
|
+
*/
|
|
58
|
+
async loadPlugins() {
|
|
59
|
+
if (!fs.existsSync(this.pluginPath)) {
|
|
60
|
+
console.log(`Plugin directory not found: ${this.pluginPath}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const pluginDirs = fs.readdirSync(this.pluginPath)
|
|
65
|
+
.filter(item => this.isDirectory(path.join(this.pluginPath, item)));
|
|
66
|
+
|
|
67
|
+
console.log(`Found ${pluginDirs.length} potential plugins in ${this.pluginPath}`);
|
|
68
|
+
|
|
69
|
+
for (const pluginDir of pluginDirs) {
|
|
70
|
+
await this.loadPlugin(pluginDir);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load a single plugin
|
|
76
|
+
* @param {string} pluginDir - Name of the plugin directory
|
|
77
|
+
*/
|
|
78
|
+
async loadPlugin(pluginDir) {
|
|
79
|
+
const pluginPath = path.join(this.pluginPath, pluginDir);
|
|
80
|
+
const manifestPath = path.join(pluginPath, 'manifest.json');
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(manifestPath)) {
|
|
83
|
+
console.warn(`Plugin ${pluginDir} does not have manifest.json, skipping...`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
89
|
+
|
|
90
|
+
if (!this.validateManifest(manifest)) {
|
|
91
|
+
console.error(`Invalid manifest for plugin ${pluginDir}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check Kukuy version compatibility
|
|
96
|
+
if (!this.isCompatibleVersion(manifest.kukuyVersion)) {
|
|
97
|
+
console.error(`Plugin ${pluginDir} requires incompatible Kukuy version: ${manifest.kukuyVersion}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!manifest.enabled) {
|
|
102
|
+
console.log(`Plugin ${pluginDir} is disabled`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const mainPath = path.join(pluginPath, manifest.main || 'index.js');
|
|
107
|
+
if (!fs.existsSync(mainPath)) {
|
|
108
|
+
console.error(`Main file not found for plugin ${pluginDir}: ${mainPath}`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Resolve the path to avoid conflicts with other requires
|
|
113
|
+
const pluginModule = require(path.resolve(mainPath));
|
|
114
|
+
const pluginInstance = new PluginInstance(pluginDir, manifest, pluginModule, this.balancer);
|
|
115
|
+
|
|
116
|
+
this.plugins.set(pluginDir, pluginInstance);
|
|
117
|
+
await pluginInstance.activate();
|
|
118
|
+
|
|
119
|
+
console.log(`Plugin ${pluginDir} loaded successfully`);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(`Error loading plugin ${pluginDir}:`, error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Validate the plugin manifest
|
|
127
|
+
* @param {Object} manifest - The manifest object to validate
|
|
128
|
+
* @returns {boolean} - True if manifest is valid
|
|
129
|
+
*/
|
|
130
|
+
validateManifest(manifest) {
|
|
131
|
+
const requiredFields = ['name', 'version', 'description', 'main'];
|
|
132
|
+
const isValid = requiredFields.every(field => manifest.hasOwnProperty(field));
|
|
133
|
+
|
|
134
|
+
if (!isValid) {
|
|
135
|
+
console.error('Missing required fields in manifest:', requiredFields.filter(field => !manifest.hasOwnProperty(field)));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return isValid;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if the plugin is compatible with current Kukuy version
|
|
143
|
+
* @param {string} requiredVersion - Version requirement from manifest
|
|
144
|
+
* @returns {boolean} - True if compatible
|
|
145
|
+
*/
|
|
146
|
+
isCompatibleVersion(requiredVersion) {
|
|
147
|
+
if (!requiredVersion) {
|
|
148
|
+
// If no version specified, assume compatibility
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Simple version check - in a real implementation, you'd want to use semver
|
|
153
|
+
const currentVersion = require('../../package.json').version;
|
|
154
|
+
|
|
155
|
+
// For now, just check if the major version matches or if it's a compatible range
|
|
156
|
+
if (requiredVersion.startsWith('^')) {
|
|
157
|
+
const requiredMajor = requiredVersion.slice(1).split('.')[0];
|
|
158
|
+
const currentMajor = currentVersion.split('.')[0];
|
|
159
|
+
return currentMajor === requiredMajor;
|
|
160
|
+
} else if (requiredVersion.startsWith('~')) {
|
|
161
|
+
const [reqMajor, reqMinor] = requiredVersion.slice(1).split('.');
|
|
162
|
+
const [curMajor, curMinor] = currentVersion.split('.');
|
|
163
|
+
return currentMajor === reqMajor && currentMinor === reqMinor;
|
|
164
|
+
} else {
|
|
165
|
+
return currentVersion === requiredVersion;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get information about loaded plugins
|
|
171
|
+
* @returns {Array} - Array of plugin information
|
|
172
|
+
*/
|
|
173
|
+
getLoadedPlugins() {
|
|
174
|
+
return Array.from(this.plugins.values()).map(plugin => ({
|
|
175
|
+
name: plugin.name,
|
|
176
|
+
version: plugin.manifest.version,
|
|
177
|
+
description: plugin.manifest.description,
|
|
178
|
+
isActive: plugin.isActive
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = { PluginManager };
|
|
@@ -15,34 +15,40 @@ class HealthChecker {
|
|
|
15
15
|
port: parsedUrl.port,
|
|
16
16
|
path: '/health', // Ruta estándar para verificación de salud
|
|
17
17
|
method: 'GET',
|
|
18
|
-
timeout: this.timeout
|
|
18
|
+
timeout: this.timeout,
|
|
19
|
+
// Agregar headers para identificar la solicitud de health check
|
|
20
|
+
headers: {
|
|
21
|
+
'User-Agent': 'Kukuy-Health-Check/1.0'
|
|
22
|
+
}
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
return new Promise((resolve) => {
|
|
22
|
-
const request = server.protocol === 'https:'
|
|
26
|
+
const request = server.protocol === 'https:'
|
|
23
27
|
? https.request(options)
|
|
24
28
|
: http.request(options);
|
|
25
29
|
|
|
26
30
|
request.on('response', (res) => {
|
|
31
|
+
// Consumir el cuerpo de la respuesta para liberar recursos
|
|
32
|
+
res.resume();
|
|
33
|
+
|
|
27
34
|
// Considerar saludable si obtenemos una respuesta exitosa
|
|
28
35
|
const isHealthy = res.statusCode >= 200 && res.statusCode < 400;
|
|
29
36
|
resolve(isHealthy);
|
|
30
37
|
});
|
|
31
38
|
|
|
32
39
|
request.on('error', (err) => {
|
|
33
|
-
|
|
40
|
+
// No imprimir errores de health check en consola para evitar spam
|
|
34
41
|
resolve(false);
|
|
35
42
|
});
|
|
36
43
|
|
|
37
44
|
request.on('timeout', () => {
|
|
38
|
-
|
|
45
|
+
// No imprimir errores de timeout de health check en consola
|
|
39
46
|
resolve(false);
|
|
40
47
|
});
|
|
41
48
|
|
|
42
49
|
request.end();
|
|
43
50
|
});
|
|
44
51
|
} catch (error) {
|
|
45
|
-
console.error(`Error en la verificación de salud: ${error.message}`);
|
|
46
52
|
return false;
|
|
47
53
|
}
|
|
48
54
|
}
|