Files
dtic-DIIAA/adn/tools/cli/dron/dispatcher.rb
T
Ricardo Monla 892019fa22 [A04.P005] DASUTEN: Pipeline refactorizado + Documentación + Informes
Refactorización del pipeline de backups DASUTEN:

1. Flujo simplificado (un solo destino):
   - Backup directo a X:\ (share srv-ns8)
   - Eliminado paso intermedio E:\BK_SQL\sysdasuten\
   - Reducido de 3 ubicaciones a 2 (X: → /var/tmp/)

2. Módulo común DasuExecutor (lib/dasu_executor.rb):
   - Centraliza ejecución PowerShell en dasu-sql4
   - Centraliza verificación de archivos en fenix
   - Elimina código repetido en 6 drones (-52% líneas)

3. Drones refactorizados:
   - dasuten_exportar_srvv-fenix.rb (FULL → X:)
   - dasuten_exportar_diferencial_srvv-fenix.rb (DIF → X:)
   - dasuten_transferir_srvv-fenix-srv-ns8.rb (simplificado)
   - dasuten_restaurar_dasu-sql4.rb (usa DasuExecutor)
   - dasuten_restaurar_diferencial_dasu-sql4.rb (usa DasuExecutor)
   - dasuten_download_drive-dasu-sql4.rb (usa DasuExecutor)
   - dasuten_verificar-integridad_dasu-sql4.rb (usa DasuExecutor)

4. Integración BKPs mantenida:
   - T8-T15: Tareas individuales
   - C7: dasuten_full (pipeline semanal)
   - C8: dasuten_diferencial (pipeline diario)

5. Documentación actualizada:
   - A04.P006_Backups-DASUTEN.md: flujo simplificado + módulo común
   - Lecciones aprendidas agregadas

6. Nuevos archivos:
   - Informes técnicos DASUTEN (docs/informes/2026-04_DASUTEN-migracion/)
   - A01.P011/P012: Documentación de drones y pipeline
   - proc_linux.rb, ops.rb: Utilitarios del ecosistema

7. Scripts legacy (para referencia):
   - orquestador_pipeline.rb, orquestador_pipeline_diferencial.rb
   - dasuten_upload_srv-ns8-drive.rb
2026-04-14 20:06:06 -03:00

295 lines
9.0 KiB
Ruby

# frozen_string_literal: true
# Dron::Dispatcher — Dispatcher de subcomandos para AtomicDrones
#
# Responsabilidad única: Despachar comandos a los módulos atómicos correctos
# - Parsea argumentos de línea de comandos
# - Invoca el módulo atómico correspondiente
# - Muestra ayuda formateada
#
# Uso:
# require_relative 'dron/dispatcher'
# Dron::Dispatcher.ejecutar(ARGV)
require_relative '../../core/help_formatter'
require_relative '../../core/logger'
require_relative '../../core/colores'
module Dron
class Dispatcher
def self.ejecutar(args)
@logger ||= ADN::Logger.new
subcomando = args.shift
case subcomando
when 'lanzar', 'run', nil
EjecutorComando.ejecutar(args)
when 'flota', 'list'
VigilanteComando.flota(args)
when 'salud', 'health'
VigilanteComando.salud(args)
when 'sanear', 'clean'
SanadorComando.sanear(args)
when 'limpiar', 'cleanup'
SanadorComando.limpiar(args)
when 'reintentar', 'retry'
SanadorComando.reintentar(args)
when 'estado', 'status'
VigilanteComando.estado(args)
when 'ops', 'operaciones'
OpsComando.ejecutar(args)
when 'ayuda', 'help', '--help', '-h'
mostrar_ayuda
else
@logger.error("❌ Subcomando desconocido: #{subcomando}")
mostrar_ayuda
exit 1
end
end
def self.mostrar_ayuda
lines = []
lines << "#{Color::BOLD}🛸 Sistema de Drones ADN (AtomicDrones)#{Color::RESET}"
lines << "#{Color::DIM}#{'=' * 50}#{Color::RESET}"
lines << ""
lines << "#{Color::CYAN}Gestión de drones atómicos especializados para automatización de tareas#{Color::RESET}"
lines << ""
lines << "Uso: #{Color::YELLOW}./adn/tools/run dron <subcomando> [opciones]#{Color::RESET}"
lines << ""
lines << "\n#{Color::BOLD}Subcomandos:#{Color::RESET}"
lines << " #{Color::GREEN}lanzar [OPTIONS]#{Color::RESET} Lanzar tarea como dron ejecutor"
lines << " #{Color::GREEN}flota#{Color::RESET} Dashboard compacto de la flota"
lines << " #{Color::GREEN}salud#{Color::RESET} Health check activo (detecta zombies)"
lines << " #{Color::GREEN}estado <ID>#{Color::RESET} Estado detallado de un dron"
lines << " #{Color::GREEN}ops <acción>#{Color::RESET} Operaciones de infraestructura (update_os)"
lines << " #{Color::GREEN}sanear#{Color::RESET} Sanear flota (reintentar + limpiar)"
lines << " #{Color::GREEN}limpiar#{Color::RESET} Limpiar drones antiguos"
lines << " #{Color::GREEN}reintentar#{Color::RESET} Re-intentar drones fallidos"
lines << ""
lines << "\n#{Color::BOLD}Opciones:#{Color::RESET}"
lines << " #{Color::GREEN}--nota \"texto\"#{Color::RESET} Nota descriptiva para la bitácora"
lines << " #{Color::GREEN}--evento ID#{Color::RESET} Vincular a evento existente"
lines << " #{Color::GREEN}--nodo NOMBRE#{Color::RESET} Nodo para bitácora (auto-detect si no se especifica)"
lines << " #{Color::GREEN}--timeout N#{Color::RESET} Timeout en segundos (default: 3600)"
lines << " #{Color::GREEN}--dry-run#{Color::RESET} Simulación sin ejecutar"
lines << " #{Color::GREEN}--help#{Color::RESET} Mostrar esta ayuda"
lines << ""
lines << "\n#{Color::BOLD}Ejemplos:#{Color::RESET}"
lines << " #{Color::DIM}$ ./adn/tools/run dron lanzar --nota 'Backup SQL' -- vzdump 103#{Color::RESET}"
lines << " #{Color::CYAN}Lanzar backup como dron#{Color::RESET}"
lines << " #{Color::DIM}$ ./adn/tools/run dron ops update_os --target linux#{Color::RESET}"
lines << " #{Color::CYAN}Actualizar OS de todos los nodos Linux#{Color::RESET}"
lines << " #{Color::DIM}$ ./adn/tools/run dron flota#{Color::RESET}"
lines << " #{Color::CYAN}Ver estado de la flota#{Color::RESET}"
lines << " #{Color::DIM}$ ./adn/tools/run dron salud#{Color::RESET}"
lines << " #{Color::CYAN}Health check de la flota#{Color::RESET}"
lines << ""
puts lines.join("\n")
end
end
# ============================================================================
# Comandos encapsulados para cada módulo atómico
# ============================================================================
class EjecutorComando
def self.ejecutar(args)
require_relative 'ejecutor'
# Parsear argumentos
opciones = parsear_opciones(args)
if opciones[:cmd].nil?
puts "❌ Error: Debes especificar un comando a ejecutar"
puts ""
puts "Uso: ./adn/tools/run dron lanzar --nota \"Nota\" -- <comando>"
exit 1
end
# Lanzar dron ejecutor
resultado = Dron::Ejecutor.lanzar(
cmd: opciones[:cmd],
nota: opciones[:nota],
evento_id: opciones[:evento_id],
timeout: opciones[:timeout],
flujo_id: opciones[:flujo_id],
nodo: opciones[:nodo]
)
# Mostrar resultado
puts "\n🛸 Resultado del Dron Ejecutor"
puts "═" * 60
puts " Dron ID: #{resultado[:dron_id]}"
puts " Estado: #{resultado[:exit_code] == 0 ? '✅ Completado' : '❌ Fallido'}"
puts " Exit Code: #{resultado[:exit_code]}"
puts " Duración: #{resultado[:duration]}s"
puts "═" * 60
exit resultado[:exit_code]
end
def self.parsear_opciones(args)
opciones = {
nota: nil,
evento_id: nil,
timeout: 3600,
flujo_id: nil,
nodo: nil,
cmd: nil
}
# Parsear opciones con nombre
while args.first&.start_with?('--')
arg = args.shift
case arg
when '--nota'
opciones[:nota] = args.shift
when '--evento'
opciones[:evento_id] = args.shift.to_i
when '--timeout'
opciones[:timeout] = args.shift.to_i
when '--flujo'
opciones[:flujo_id] = args.shift
when '--nodo'
opciones[:nodo] = args.shift
when '--help', '-h'
Dispatcher.mostrar_ayuda
exit 0
end
end
# El resto es el comando a ejecutar
opciones[:cmd] = args.join(' ') unless args.empty?
opciones
end
end
class VigilanteComando
def self.flota(args)
require_relative 'vigilante'
output = Dron::Vigilante.dashboard
puts output
end
def self.salud(args)
require_relative 'vigilante'
diagnosticos = Dron::Vigilante.salud
exit(diagnosticos[:saludable] ? 0 : 1)
end
def self.estado(args)
require_relative 'vigilante'
require_relative '../../db/core/dron_db'
dron_id = args.first
if dron_id.nil?
puts "❌ Error: Debes especificar un dron_id"
puts "Uso: ./adn/tools/run dron estado <dron_id>"
exit 1
end
dron = ADN::DB::DronDB.obtener_dron(dron_id)
if dron.nil?
puts "❌ Dron no encontrado: #{dron_id}"
exit 1
end
puts "\n📊 Estado del Dron"
puts "═" * 60
puts " ID: #{dron['dron_id']}"
puts " Tipo: #{dron['tipo']}"
puts " Estado: #{dron['estado']}"
puts " Comando: #{dron['cmd']}"
puts " Inicio: #{dron['started_at']}"
puts " Fin: #{dron['completed_at'] || '—'}"
puts " Heartbeat: #{dron['heartbeat']}"
puts " Reintentos: #{dron['reintentos']}"
puts " Exit Code: #{dron['exit_code'] || '—'}"
puts "═" * 60
# Mostrar avances si existen
require_relative '../../db/core/dron_db'
avances = ADN::DB::DronDB.obtener_avances(dron_id)
unless avances.empty?
puts "\n📝 Avances:"
avances.each do |avance|
puts " [#{avance['estado']}] Paso #{avance['paso']}: #{avance['descripcion']}"
end
end
end
end
class SanadorComando
def self.sanear(args)
require_relative 'sanador'
opciones = parsear_opciones(args)
resultado = Dron::Sanador.sanear(
dias_limpieza: opciones[:dias],
dry_run: opciones[:dry_run]
)
exit(0)
end
def self.limpiar(args)
require_relative 'sanador'
opciones = parsear_opciones(args)
resultado = Dron::Sanador.limpiar_antiguos(
dias: opciones[:dias],
dry_run: opciones[:dry_run]
)
exit(0)
end
def self.reintentar(args)
require_relative 'sanador'
opciones = parsear_opciones(args)
resultado = Dron::Sanador.reintentar_fallidos(
dry_run: opciones[:dry_run]
)
exit(0)
end
def self.parsear_opciones(args)
opciones = {
dias: 7,
dry_run: false
}
args.each do |arg|
case arg
when '--dry-run'
opciones[:dry_run] = true
when '--dias'
idx = args.index(arg)
opciones[:dias] = args[idx + 1].to_i if args[idx + 1]
end
end
opciones
end
end
class OpsComando
def self.ejecutar(args)
require_relative 'ops'
Dron::Ops.ejecutar(args)
end
end
end