kukuy 1.5.0 → 1.9.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 +159 -188
- package/balancer.log +30 -0
- package/certs/auto/certificate.crt +22 -0
- package/certs/auto/private.key +28 -0
- package/kukuy-plugins/README.md +125 -0
- package/kukuy-plugins/cache-plugin/index.js +477 -0
- package/kukuy-plugins/cache-plugin/manifest.json +17 -0
- package/kukuy-plugins/ejemplo-plugin/index.js +41 -0
- package/kukuy-plugins/ejemplo-plugin/manifest.json +11 -0
- package/kukuy-plugins/health-checker/index.js +168 -0
- package/kukuy-plugins/health-checker/manifest.json +16 -0
- package/kukuy-plugins/health-monitor/index.js +58 -0
- package/kukuy-plugins/health-monitor/manifest.json +16 -0
- package/kukuy-plugins/redirect-plugin/index.js +172 -0
- package/kukuy-plugins/redirect-plugin/manifest.json +15 -0
- package/package.json +7 -3
- package/servers_real.json +5 -0
- package/src/core/Balancer.js +176 -39
- package/src/core/ServerPool.js +2 -2
- package/src/extensibility/ExtendedFilterChain.js +90 -0
- package/src/extensibility/ExtendedHookManager.js +87 -0
- package/src/extensibility/FilterChain.js +2 -9
- package/src/extensibility/HookManager.js +1 -0
- package/src/extensibility/PostStartupExtension.js +97 -0
- package/src/plugins/PluginManager.js +231 -0
- package/src/utils/HealthChecker.js +61 -6
- package/.ctagsd/ctagsd.json +0 -954
- package/.ctagsd/file_list.txt +0 -100
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +0 -125
- package/LICENSE +0 -680
- package/README-SSL.md +0 -165
- package/captura.png +0 -0
- package/kukuu1.webp +0 -0
- package/kukuy.workspace +0 -11
- package/optimize-mariadb.sh +0 -152
- package/restart-balancer.sh +0 -10
- package/scripts/load_test.py +0 -151
- package/stress-test.js +0 -190
- package/test_optimization.js +0 -54
|
@@ -0,0 +1,231 @@
|
|
|
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
|
+
* Print information about available and loaded plugins to stdout
|
|
184
|
+
*/
|
|
185
|
+
printPluginsInfo() {
|
|
186
|
+
console.log('\n========== INFORMACIÓN DE PLUGINS ==========');
|
|
187
|
+
|
|
188
|
+
if (!fs.existsSync(this.pluginPath)) {
|
|
189
|
+
console.log(`Directorio de plugins no encontrado: ${this.pluginPath}`);
|
|
190
|
+
console.log('=============================================\n');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const allItems = fs.readdirSync(this.pluginPath);
|
|
195
|
+
const pluginDirs = allItems.filter(item => this.isDirectory(path.join(this.pluginPath, item)));
|
|
196
|
+
|
|
197
|
+
console.log(`Plugins encontrados en ${this.pluginPath}: ${pluginDirs.length}`);
|
|
198
|
+
|
|
199
|
+
for (const pluginDir of pluginDirs) {
|
|
200
|
+
const pluginPath = path.join(this.pluginPath, pluginDir);
|
|
201
|
+
const manifestPath = path.join(pluginPath, 'manifest.json');
|
|
202
|
+
|
|
203
|
+
if (fs.existsSync(manifestPath)) {
|
|
204
|
+
try {
|
|
205
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
206
|
+
|
|
207
|
+
if (this.plugins.has(pluginDir)) {
|
|
208
|
+
const plugin = this.plugins.get(pluginDir);
|
|
209
|
+
console.log(`✓ ACTIVO - ${pluginDir} (v${manifest.version}): ${manifest.description}`);
|
|
210
|
+
} else {
|
|
211
|
+
if (manifest.enabled !== false) {
|
|
212
|
+
console.log(`○ DISPONIBLE - ${pluginDir} (v${manifest.version}): ${manifest.description}`);
|
|
213
|
+
} else {
|
|
214
|
+
console.log(`○ DESHABILITADO - ${pluginDir} (v${manifest.version}): ${manifest.description} (manifest: enabled=false)`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.log(`✗ ERROR - ${pluginDir}: Error leyendo manifest.json`);
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
console.log(`✗ SIN MANIFEST - ${pluginDir}: No tiene archivo manifest.json`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const loadedCount = this.plugins.size;
|
|
226
|
+
console.log(`\nTotal de plugins activos: ${loadedCount}`);
|
|
227
|
+
console.log('=============================================\n');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = { PluginManager };
|
|
@@ -3,8 +3,9 @@ const https = require('https');
|
|
|
3
3
|
const url = require('url');
|
|
4
4
|
|
|
5
5
|
class HealthChecker {
|
|
6
|
-
constructor() {
|
|
6
|
+
constructor(balancer = null) {
|
|
7
7
|
this.timeout = 5000; // 5 segundos de timeout
|
|
8
|
+
this.balancer = balancer; // Referencia al balanceador para acceder a los hooks
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
async checkServerHealth(server) {
|
|
@@ -22,6 +23,9 @@ class HealthChecker {
|
|
|
22
23
|
}
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
// Emitir hook antes de realizar el check
|
|
27
|
+
await this.executeHook('onHealthCheckStart', { server, checkTime: new Date() });
|
|
28
|
+
|
|
25
29
|
return new Promise((resolve) => {
|
|
26
30
|
const request = server.protocol === 'https:'
|
|
27
31
|
? https.request(options)
|
|
@@ -33,25 +37,76 @@ class HealthChecker {
|
|
|
33
37
|
|
|
34
38
|
// Considerar saludable si obtenemos una respuesta exitosa
|
|
35
39
|
const isHealthy = res.statusCode >= 200 && res.statusCode < 400;
|
|
36
|
-
|
|
40
|
+
|
|
41
|
+
// Emitir hook después de completar el check
|
|
42
|
+
this.executeHook('onHealthCheckComplete', {
|
|
43
|
+
server,
|
|
44
|
+
isHealthy,
|
|
45
|
+
statusCode: res.statusCode,
|
|
46
|
+
checkTime: new Date()
|
|
47
|
+
}).then(() => {
|
|
48
|
+
// Resolver la promesa después de emitir el hook
|
|
49
|
+
resolve(isHealthy);
|
|
50
|
+
}).catch(() => {
|
|
51
|
+
// En caso de error al emitir el hook, resolver igualmente
|
|
52
|
+
resolve(isHealthy);
|
|
53
|
+
});
|
|
37
54
|
});
|
|
38
55
|
|
|
39
56
|
request.on('error', (err) => {
|
|
40
|
-
//
|
|
41
|
-
|
|
57
|
+
// Emitir hook cuando ocurre un error en el check
|
|
58
|
+
this.executeHook('onHealthCheckError', {
|
|
59
|
+
server,
|
|
60
|
+
error: err,
|
|
61
|
+
checkTime: new Date()
|
|
62
|
+
}).then(() => {
|
|
63
|
+
// No imprimir errores de health check en consola para evitar spam
|
|
64
|
+
resolve(false);
|
|
65
|
+
}).catch(() => {
|
|
66
|
+
resolve(false);
|
|
67
|
+
});
|
|
42
68
|
});
|
|
43
69
|
|
|
44
70
|
request.on('timeout', () => {
|
|
45
|
-
//
|
|
46
|
-
|
|
71
|
+
// Emitir hook cuando ocurre un timeout en el check
|
|
72
|
+
this.executeHook('onHealthCheckTimeout', {
|
|
73
|
+
server,
|
|
74
|
+
checkTime: new Date()
|
|
75
|
+
}).then(() => {
|
|
76
|
+
// No imprimir errores de timeout de health check en consola
|
|
77
|
+
resolve(false);
|
|
78
|
+
}).catch(() => {
|
|
79
|
+
resolve(false);
|
|
80
|
+
});
|
|
47
81
|
});
|
|
48
82
|
|
|
49
83
|
request.end();
|
|
50
84
|
});
|
|
51
85
|
} catch (error) {
|
|
86
|
+
// Emitir hook cuando ocurre una excepción
|
|
87
|
+
await this.executeHook('onHealthCheckException', {
|
|
88
|
+
server,
|
|
89
|
+
error,
|
|
90
|
+
checkTime: new Date()
|
|
91
|
+
});
|
|
52
92
|
return false;
|
|
53
93
|
}
|
|
54
94
|
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Execute a hook if the balancer reference is available
|
|
98
|
+
* @param {string} hookName - Name of the hook to execute
|
|
99
|
+
* @param {Object} data - Data to pass to the hook
|
|
100
|
+
*/
|
|
101
|
+
async executeHook(hookName, data) {
|
|
102
|
+
if (this.balancer && this.balancer.hookManager) {
|
|
103
|
+
try {
|
|
104
|
+
await this.balancer.hookManager.executeHooks(hookName, data);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(`Error executing ${hookName} hook:`, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
55
110
|
}
|
|
56
111
|
|
|
57
112
|
module.exports = { HealthChecker };
|