Files
dtic-DIIAA/adn/tools/cli/dron/ejecutor.rb
T
Ricardo MonlaandClaude Opus 4.6 be3a78c50a fix(dron): Ejecutor funcional + tabla events
Cambios:
- ejecutor.rb: Corregir lectura de output con Open3
- bitacora_db.rb: Agregar métodos crear_evento/actualizar_evento (public)
- dron_db.rb: Corregir conversión de PG::Result a Hash (.first.to_h)
- vigilante.rb: Corregir formato de fecha en dashboard
- migrations/004: Crear tabla bitacoras.events para Bitácora Web

Pruebas:
- dron lanzar --evento AUTO --nota "Test" -- echo "hola" 
- dron flota 
- dron salud 

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 12:34:20 -03:00

93 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}")
{ dron_id: nil, 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 = []
exit_code = nil
heartbeat_thread = Thread.new do
loop do
sleep(HEARTBEAT_INTERVAL)
ADN::DB::DronDB.actualizar_heartbeat(dron_id)
end
end
begin
require 'open3'
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
# Leer stdout y stderr en threads separados
out_thread = Thread.new { output << stdout.read }
err_thread = Thread.new { output << stderr.read }
out_thread.join
err_thread.join
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 || 1, output: output.join("\n").strip, 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.with_connection do |db|
db.crear_evento(nodo_id: 1, descripcion: descripcion, inicio: Time.now.strftime('%H:%M'), estado: '⏳')
end
else
estado = resultado[:exit_code] == 0 ? '✅' : '❌'
descripcion = "🛸 dron: #{nota}#{estado} (#{Base.formato_duracion(resultado[:duration])})"
BitacorasDB::BitacoraDB.with_connection do |db|
db.actualizar_evento(evento_id, descripcion: descripcion, fin: Time.now.strftime('%H:%M'))
end
end
rescue StandardError => e
Base.logger.warn("⚠️ Error en bitácora: #{e.message}")
nil
end
end
end
end