kukuy 1.4.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/.ctagsd/ctagsd.json +954 -0
- package/.ctagsd/file_list.txt +100 -0
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +101 -0
- package/LICENSE +680 -0
- package/README.md +251 -0
- package/captura.png +0 -0
- package/kukuy.js +23 -0
- package/kukuy.workspace +11 -0
- package/package.json +26 -0
- package/restart-balancer.sh +10 -0
- package/routes.json +14 -0
- package/scripts/load_test.py +151 -0
- package/servers.json +19 -0
- package/servers_real.json +19 -0
- package/src/algorithms/AlgorithmManager.js +85 -0
- package/src/algorithms/IPHashAlgorithm.js +131 -0
- package/src/algorithms/LoadBalancingAlgorithm.js +23 -0
- package/src/algorithms/RoundRobinAlgorithm.js +67 -0
- package/src/config/ConfigManager.js +37 -0
- package/src/config/RouteLoader.js +36 -0
- package/src/core/Balancer.js +353 -0
- package/src/core/RoundRobinAlgorithm.js +60 -0
- package/src/core/ServerPool.js +77 -0
- package/src/dashboard/WebDashboard.js +150 -0
- package/src/dashboard/WebSocketServer.js +114 -0
- package/src/extensibility/CachingFilter.js +134 -0
- package/src/extensibility/FilterChain.js +93 -0
- package/src/extensibility/HookManager.js +48 -0
- package/src/protocol/HttpBalancer.js +37 -0
- package/src/protocol/HttpsBalancer.js +47 -0
- package/src/utils/BalancerLogger.js +102 -0
- package/src/utils/HealthChecker.js +51 -0
- package/src/utils/Logger.js +39 -0
- package/src/utils/MetricsCollector.js +82 -0
- package/src/utils/ProfessionalMetrics.js +501 -0
- package/start-iphash.sh +5 -0
- package/start-roundrobin.sh +5 -0
- package/stress-test.js +190 -0
- package/webpage/README.md +17 -0
- package/webpage/index.html +549 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const https = require('https');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { Logger } = require('../utils/Logger');
|
|
5
|
+
|
|
6
|
+
class HttpBalancer {
|
|
7
|
+
constructor(port, requestHandler) {
|
|
8
|
+
this.port = port;
|
|
9
|
+
this.requestHandler = requestHandler;
|
|
10
|
+
this.logger = new Logger();
|
|
11
|
+
this.server = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
start() {
|
|
15
|
+
this.server = http.createServer((req, res) => {
|
|
16
|
+
this.requestHandler(req, res);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
this.server.listen(this.port, () => {
|
|
20
|
+
this.logger.info(`Balanceador HTTP escuchando en puerto ${this.port}`);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return this.server;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
stop() {
|
|
27
|
+
if (this.server) {
|
|
28
|
+
this.server.close();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isRunning() {
|
|
33
|
+
return this.server && this.server.listening;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { HttpBalancer };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { Logger } = require('../utils/Logger');
|
|
4
|
+
|
|
5
|
+
class HttpsBalancer {
|
|
6
|
+
constructor(port, sslCertPath, sslKeyPath, requestHandler) {
|
|
7
|
+
this.port = port;
|
|
8
|
+
this.sslCertPath = sslCertPath;
|
|
9
|
+
this.sslKeyPath = sslKeyPath;
|
|
10
|
+
this.requestHandler = requestHandler;
|
|
11
|
+
this.logger = new Logger();
|
|
12
|
+
this.server = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
start() {
|
|
16
|
+
if (!this.sslCertPath || !this.sslKeyPath) {
|
|
17
|
+
throw new Error('Rutas al certificado SSL y llave privada son requeridas para HTTPS');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const options = {
|
|
21
|
+
cert: fs.readFileSync(this.sslCertPath),
|
|
22
|
+
key: fs.readFileSync(this.sslKeyPath)
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
this.server = https.createServer(options, (req, res) => {
|
|
26
|
+
this.requestHandler(req, res);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.server.listen(this.port, () => {
|
|
30
|
+
this.logger.info(`Balanceador HTTPS escuchando en puerto ${this.port}`);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return this.server;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
stop() {
|
|
37
|
+
if (this.server) {
|
|
38
|
+
this.server.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
isRunning() {
|
|
43
|
+
return this.server && this.server.listening;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { HttpsBalancer };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { ConfigManager } = require('../config/ConfigManager');
|
|
3
|
+
|
|
4
|
+
class BalancerLogger {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.config = ConfigManager.getInstance();
|
|
7
|
+
this.logLevel = this.config.logLevel || 'info';
|
|
8
|
+
this.logFile = process.env.LOG_FILE_PATH || './balancer.log';
|
|
9
|
+
this.levels = {
|
|
10
|
+
error: 0,
|
|
11
|
+
warn: 1,
|
|
12
|
+
info: 2,
|
|
13
|
+
debug: 3
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
log(level, message, metadata = {}) {
|
|
18
|
+
if (this.levels[level] <= this.levels[this.logLevel]) {
|
|
19
|
+
const timestamp = new Date().toISOString();
|
|
20
|
+
const logEntry = {
|
|
21
|
+
timestamp,
|
|
22
|
+
level: level.toUpperCase(),
|
|
23
|
+
message,
|
|
24
|
+
...metadata
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const logLine = JSON.stringify(logEntry) + '\n';
|
|
28
|
+
|
|
29
|
+
// Imprimir en consola
|
|
30
|
+
console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`, metadata);
|
|
31
|
+
|
|
32
|
+
// Escribir en archivo de log
|
|
33
|
+
try {
|
|
34
|
+
fs.appendFileSync(this.logFile, logLine);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error('Error escribiendo en archivo de log:', err.message);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
error(message, metadata = {}) {
|
|
42
|
+
this.log('error', message, metadata);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
warn(message, metadata = {}) {
|
|
46
|
+
this.log('warn', message, metadata);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
info(message, metadata = {}) {
|
|
50
|
+
this.log('info', message, metadata);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
debug(message, metadata = {}) {
|
|
54
|
+
this.log('debug', message, metadata);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Métodos específicos para el balanceador
|
|
58
|
+
logOnlineTarget(server, requestInfo = {}) {
|
|
59
|
+
this.info(`Servidor ONLINE - ID: ${server.id}, URL: ${server.url}`, {
|
|
60
|
+
eventType: 'target_online',
|
|
61
|
+
serverId: server.id,
|
|
62
|
+
serverUrl: server.url,
|
|
63
|
+
request: requestInfo,
|
|
64
|
+
timestamp: new Date().toISOString()
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
logOfflineTarget(server, error, requestInfo = {}) {
|
|
69
|
+
this.warn(`Servidor OFFLINE - ID: ${server.id}, URL: ${server.url}, Error: ${error.message}`, {
|
|
70
|
+
eventType: 'target_offline',
|
|
71
|
+
serverId: server.id,
|
|
72
|
+
serverUrl: server.url,
|
|
73
|
+
error: error.message,
|
|
74
|
+
request: requestInfo,
|
|
75
|
+
timestamp: new Date().toISOString()
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
logTargetSelection(server, algorithm = 'round_robin') {
|
|
80
|
+
this.debug(`Seleccionado servidor destino - ID: ${server.id}, URL: ${server.url}`, {
|
|
81
|
+
eventType: 'target_selected',
|
|
82
|
+
serverId: server.id,
|
|
83
|
+
serverUrl: server.url,
|
|
84
|
+
algorithm,
|
|
85
|
+
timestamp: new Date().toISOString()
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
logLoadBalancingStats(stats) {
|
|
90
|
+
this.info('Estadísticas de balanceo de carga', {
|
|
91
|
+
eventType: 'balancing_stats',
|
|
92
|
+
totalRequests: stats.totalRequests,
|
|
93
|
+
successfulRequests: stats.successfulRequests,
|
|
94
|
+
failedRequests: stats.failedRequests,
|
|
95
|
+
onlineTargets: stats.onlineTargets,
|
|
96
|
+
offlineTargets: stats.offlineTargets,
|
|
97
|
+
timestamp: new Date().toISOString()
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { BalancerLogger };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const https = require('https');
|
|
3
|
+
const url = require('url');
|
|
4
|
+
|
|
5
|
+
class HealthChecker {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.timeout = 5000; // 5 segundos de timeout
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async checkServerHealth(server) {
|
|
11
|
+
try {
|
|
12
|
+
const parsedUrl = url.parse(server.url);
|
|
13
|
+
const options = {
|
|
14
|
+
hostname: parsedUrl.hostname,
|
|
15
|
+
port: parsedUrl.port,
|
|
16
|
+
path: '/health', // Ruta estándar para verificación de salud
|
|
17
|
+
method: 'GET',
|
|
18
|
+
timeout: this.timeout
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const request = server.protocol === 'https:'
|
|
23
|
+
? https.request(options)
|
|
24
|
+
: http.request(options);
|
|
25
|
+
|
|
26
|
+
request.on('response', (res) => {
|
|
27
|
+
// Considerar saludable si obtenemos una respuesta exitosa
|
|
28
|
+
const isHealthy = res.statusCode >= 200 && res.statusCode < 400;
|
|
29
|
+
resolve(isHealthy);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
request.on('error', (err) => {
|
|
33
|
+
console.error(`Error verificando salud de ${server.url}: ${err.message}`);
|
|
34
|
+
resolve(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
request.on('timeout', () => {
|
|
38
|
+
console.error(`Timeout verificando salud de ${server.url}`);
|
|
39
|
+
resolve(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
request.end();
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(`Error en la verificación de salud: ${error.message}`);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { HealthChecker };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const { ConfigManager } = require('../config/ConfigManager');
|
|
2
|
+
|
|
3
|
+
class Logger {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.config = ConfigManager.getInstance();
|
|
6
|
+
this.level = this.config.logLevel;
|
|
7
|
+
this.levels = {
|
|
8
|
+
error: 0,
|
|
9
|
+
warn: 1,
|
|
10
|
+
info: 2,
|
|
11
|
+
debug: 3
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
log(level, message) {
|
|
16
|
+
if (this.levels[level] <= this.levels[this.level]) {
|
|
17
|
+
const timestamp = new Date().toISOString();
|
|
18
|
+
console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
error(message) {
|
|
23
|
+
this.log('error', message);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
warn(message) {
|
|
27
|
+
this.log('warn', message);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
info(message) {
|
|
31
|
+
this.log('info', message);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
debug(message) {
|
|
35
|
+
this.log('debug', message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { Logger };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
class MetricsCollector {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.metrics = {
|
|
4
|
+
totalRequests: 0,
|
|
5
|
+
successfulRequests: 0,
|
|
6
|
+
failedRequests: 0,
|
|
7
|
+
totalResponseTime: 0,
|
|
8
|
+
minResponseTime: Infinity,
|
|
9
|
+
maxResponseTime: 0,
|
|
10
|
+
startTime: Date.now(),
|
|
11
|
+
requestsByPath: {},
|
|
12
|
+
requestsByServer: {}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
recordRequest(path, serverId, responseTime, success = true) {
|
|
17
|
+
this.metrics.totalRequests++;
|
|
18
|
+
|
|
19
|
+
if (success) {
|
|
20
|
+
this.metrics.successfulRequests++;
|
|
21
|
+
} else {
|
|
22
|
+
this.metrics.failedRequests++;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.metrics.totalResponseTime += responseTime;
|
|
26
|
+
|
|
27
|
+
if (responseTime < this.metrics.minResponseTime) {
|
|
28
|
+
this.metrics.minResponseTime = responseTime;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (responseTime > this.metrics.maxResponseTime) {
|
|
32
|
+
this.metrics.maxResponseTime = responseTime;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Contar solicitudes por ruta
|
|
36
|
+
if (!this.metrics.requestsByPath[path]) {
|
|
37
|
+
this.metrics.requestsByPath[path] = 0;
|
|
38
|
+
}
|
|
39
|
+
this.metrics.requestsByPath[path]++;
|
|
40
|
+
|
|
41
|
+
// Contar solicitudes por servidor
|
|
42
|
+
if (!this.metrics.requestsByServer[serverId]) {
|
|
43
|
+
this.metrics.requestsByServer[serverId] = 0;
|
|
44
|
+
}
|
|
45
|
+
this.metrics.requestsByServer[serverId]++;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getMetrics() {
|
|
49
|
+
const uptime = Date.now() - this.metrics.startTime;
|
|
50
|
+
const avgResponseTime = this.metrics.totalRequests > 0
|
|
51
|
+
? this.metrics.totalResponseTime / this.metrics.totalRequests
|
|
52
|
+
: 0;
|
|
53
|
+
|
|
54
|
+
const rps = uptime > 0
|
|
55
|
+
? (this.metrics.totalRequests / uptime) * 1000
|
|
56
|
+
: 0;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...this.metrics,
|
|
60
|
+
uptime,
|
|
61
|
+
avgResponseTime,
|
|
62
|
+
rps,
|
|
63
|
+
minResponseTime: this.metrics.minResponseTime === Infinity ? 0 : this.metrics.minResponseTime
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
reset() {
|
|
68
|
+
this.metrics = {
|
|
69
|
+
totalRequests: 0,
|
|
70
|
+
successfulRequests: 0,
|
|
71
|
+
failedRequests: 0,
|
|
72
|
+
totalResponseTime: 0,
|
|
73
|
+
minResponseTime: Infinity,
|
|
74
|
+
maxResponseTime: 0,
|
|
75
|
+
startTime: Date.now(),
|
|
76
|
+
requestsByPath: {},
|
|
77
|
+
requestsByServer: {}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { MetricsCollector };
|