Principio "Menos es Más": código común centralizado en un solo lugar. Cambios: - Nuevo módulo Dron::Base con funcionalidad compartida: - logger: Logger compartido (evita crear múltiples instancias) - db_query: Ejecución de SQL con resultados - db_exec: Ejecución de SQL sin resultados - formato_duracion: Utilitario para formatear segundos - slice_safe: Slice seguro de strings - blank?: Verificación de strings vacíos/nulos - Todos los módulos atómicos (ejecutor, vigilante, sanador, bitacora) ahora usan Base.logger, Base.db_query, Base.db_exec Beneficios: - Un solo lugar para corregir errores comunes - Menos duplicación de código (~40% menos líneas) - Consistencia en manejo de logs y DB - Fácil extensión: nuevos drones heredan funcionalidad automática Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
90 lines
3.4 KiB
Ruby
90 lines
3.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Dron::Ejecutor — Módulo atómico para ejecución de comandos
|
|
#
|
|
# Responsabilidad única: Ejecutar un comando y reportar resultado
|
|
|
|
require_relative 'base'
|
|
require_relative '../../db/core/dron_db'
|
|
|
|
module Dron
|
|
class Ejecutor
|
|
DEFAULT_TIMEOUT = 3600
|
|
HEARTBEAT_INTERVAL = 30
|
|
|
|
class << self
|
|
def lanzar(cmd:, nota: nil, evento_id: nil, timeout: DEFAULT_TIMEOUT, flujo_id: nil)
|
|
Base.logger.info("🛠️ Dron Ejecutor iniciado: #{Process.pid}")
|
|
dron_id = ADN::DB::DronDB.generar_dron_id
|
|
|
|
ADN::DB::DronDB.registrar_inicio(tipo: 'ejecutor', cmd: cmd, flujo_id: flujo_id, metadata: { nota: nota, evento_id: evento_id, timeout: timeout })
|
|
evento_id = registrar_bitacora(nota, evento_id, 'inicio') if nota
|
|
resultado = ejecutar_comando(cmd, dron_id, timeout)
|
|
ADN::DB::DronDB.registrar_fin(dron_id, resultado[:exit_code], resultado[:output])
|
|
registrar_bitacora(nota, evento_id, 'fin', resultado) if evento_id
|
|
|
|
Base.logger.info("🛠️ Dron Ejecutor completado: #{dron_id} - #{resultado[:exit_code] == 0 ? '✅' : '❌'}")
|
|
{ dron_id: dron_id, exit_code: resultado[:exit_code], duration: resultado[:duration], evento_id: evento_id }
|
|
rescue StandardError => e
|
|
Base.logger.error("🛠️ Dron Ejecutor falló: #{e.message}")
|
|
ADN::DB::DronDB.registrar_fin(dron_id, 1, e.message) if dron_id
|
|
{ dron_id: dron_id, exit_code: 1, output: e.message, duration: 0, error: e.message }
|
|
end
|
|
|
|
private
|
|
|
|
def ejecutar_comando(cmd, dron_id, timeout)
|
|
start_time = Time.now
|
|
output = []
|
|
|
|
heartbeat_thread = Thread.new do
|
|
loop do
|
|
sleep(HEARTBEAT_INTERVAL)
|
|
ADN::DB::DronDB.actualizar_heartbeat(dron_id)
|
|
end
|
|
end
|
|
|
|
begin
|
|
Timeout.timeout(timeout) do
|
|
require 'open3'
|
|
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
|
|
output << stdout.readline.chomp while (line = stdout.readline) && output << line
|
|
output << stderr.readline.chomp while (line = stderr.readline) && output << "[ERR] #{line}"
|
|
rescue EOFError
|
|
break
|
|
end
|
|
exit_code = wait_thr.value.exitstatus
|
|
end
|
|
rescue Timeout::Error
|
|
exit_code = 124
|
|
output << "\n[TIMEOUT] #{timeout}s excedidos"
|
|
rescue StandardError => e
|
|
exit_code = 1
|
|
output << "\n[ERROR] #{e.message}"
|
|
ensure
|
|
heartbeat_thread.exit
|
|
end
|
|
|
|
{ exit_code: exit_code, output: output.join("\n"), duration: (Time.now - start_time).round(2) }
|
|
end
|
|
|
|
def registrar_bitacora(nota, evento_id, tipo, resultado = nil)
|
|
return nil unless nota
|
|
require_relative '../../db/core/bitacora_db'
|
|
|
|
if tipo == 'inicio'
|
|
descripcion = "🛸 dron: #{nota}"
|
|
BitacorasDB::BitacoraDB.crear_evento(nodo_id: 1, descripcion: descripcion, inicio: Time.now.strftime('%H:%M'), estado: '⏳')
|
|
else
|
|
estado = resultado[:exit_code] == 0 ? '✅' : '❌'
|
|
descripcion = "🛸 dron: #{nota} — #{estado} (#{Base.formato_duracion(resultado[:duration])})"
|
|
BitacorasDB::BitacoraDB.actualizar_evento(evento_id, descripcion: descripcion, fin: Time.now.strftime('%H:%M'))
|
|
end
|
|
rescue StandardError => e
|
|
Base.logger.warn("⚠️ Error en bitácora: #{e.message}")
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
end
|