#!/usr/bin/env ruby # frozen_string_literal: true # ========================================================== # adn/tools/cli/drones/orquestador_pipeline.rb # ========================================================== # Orquestador de Pipeline DASUTEN — Intercomunicación vía JSON # ========================================================== # Flujo: exportar → transferir SMB → upload → download → restaurar → verificar # Cada dron escribe su output en JSON y el orquestador lo lee # para decidir el siguiente paso. # ========================================================== require 'optparse' require 'json' require 'time' require 'shellwords' CLI_DIR = File.expand_path(__dir__) ADN_RUN = File.join(File.dirname(File.dirname(File.dirname(CLI_DIR))), 'tools', 'run') # Configuración opciones = { paso_inicial: 'exportar', dry_run: false, timeout: 1800, # 30 min por dron nodo_bitacora: 'dasuten-pipeline' } OptionParser.new do |opts| opts.banner = "Uso: #{ADN_RUN} dron lanzar --nota 'Pipeline DASUTEN' -- ruby adn/tools/cli/drones/orquestador_pipeline.rb [opciones]" opts.on("--paso PASO", %w[exportar transferir upload download restaurar verificar], "Comenzar desde este paso") { |p| opciones[:paso_inicial] = p } opts.on("--dry-run", "Simulación sin ejecutar drones") { opciones[:dry_run] = true } opts.on("--timeout N", Integer, "Timeout por dron en segundos") { |t| opciones[:timeout] = t } end.parse!(ARGV) # Estados del pipeline (6 drones atómicos) ESTADOS = { exportar: { script: 'dasuten_exportar_srvv-fenix.rb', nodo: 'srvv-fenix', nota: '📤 Exportar backup DASUTEN', output_file: '/tmp/dron_export_output.json', siguiente: :transferir }, transferir: { script: 'dasuten_transferir_srvv-fenix-srv-ns8.rb', nodo: 'srv-ns8', nota: '🔄 Transferir SMB (fenix → ns8)', output_file: '/tmp/dron_transfer_output.json', siguiente: :upload }, upload: { script: 'dasuten_upload_srv-ns8-drive.rb', nodo: 'srv-ns8', nota: '☁️ Upload a Google Drive', output_file: '/tmp/dron_upload_output.json', siguiente: :download }, download: { script: 'dasuten_download_drive-dasu-sql4.rb', nodo: 'dasu-sql4', nota: '📥 Download desde Google Drive', output_file: '/tmp/dron_download_output.json', siguiente: :restaurar }, restaurar: { script: 'dasuten_restaurar_dasu-sql4.rb', nodo: 'dasu-sql4', nota: '💾 Restaurar backup', output_file: '/tmp/dron_restore_output.json', siguiente: :verificar }, verificar: { script: 'dasuten_verificar-integridad_dasu-sql4.rb', nodo: 'dasu-sql4', nota: '✅ Verificar integridad BD', output_file: '/tmp/dron_verify_output.json', siguiente: nil # Final } }.freeze puts "\n" + "="*70 puts "🛸 ORQUESTADOR PIPELINE DASUTEN (Fase 8b — 6 drones atómicos)" puts "="*70 puts " Paso inicial: #{opciones[:paso_inicial]}" puts " Dry-run: #{opciones[:dry_run]}" puts " Timeout: #{opciones[:timeout]}s por dron" puts " Drones: #{ESTADOS.keys.join(' → ')}" puts "="*70 # ────────────────────────────────────────────────────────────── # Función para lanzar un dron y esperar su resultado # ────────────────────────────────────────────────────────────── def lanzar_dron(script, nodo, nota, timeout) cmd = "ruby #{File.join(File.dirname(__FILE__), script)}" puts "\n" + "-"*70 puts "🛸 Lanzando dron: #{nota}" puts " Nodo: #{nodo}" puts " Script: #{script}" puts "-"*70 # Lanzar dron ejecutor dron_cmd = "#{ADN_RUN} dron lanzar --nota #{Shellwords.escape(nota)} --nodo #{Shellwords.escape(nodo)} --timeout #{timeout} -- #{cmd}" puts " Comando: #{dron_cmd}" if opciones[:dry_run] puts " [DRY-RUN] No ejecutado" return true, {} end exito = system(dron_cmd) codigo = $?.exitstatus if exito puts "\n✅ Dron completado exitosamente" # Leer output del dron output_file = ESTADOS[nodo.to_sym][:output_file] if File.exist?(output_file) resultado = JSON.parse(File.read(output_file)) puts " Output: #{resultado.inspect}" return true, resultado else puts " [!] No se encontró archivo de output" return true, {} end else puts "\n❌ Dron fallido (código: #{codigo})" return false, {} end end # ────────────────────────────────────────────────────────────── # Ejecutar pipeline # ────────────────────────────────────────────────────────────── paso_actual = opciones[:paso_inicial].to_sym contexto = {} # Para pasar datos entre drones duracioniones = [] begin loop do break unless ESTADOS[paso_actual] config = ESTADOS[paso_actual] # Inyectar contexto del paso anterior si es necesario case paso_actual when :upload # El dron de upload ya sabe dónde está el archivo (hardcodeado en /var/tmp/) when :download # El dron de download usa file_id o test_file when :restaurar # El dron de restaurar usa la ruta hardcodeada F:\BACKUP\ end # Lanzar dron exito, resultado = lanzar_dron( config[:script], config[:nodo], config[:nota], opciones[:timeout] ) unless exito puts "\n❌ Pipeline fallido en paso: #{paso_actual}" exit 1 end # Guardar contexto para siguiente paso contexto.merge!(resultado) if resultado duraciones << resultado[:duracion] if resultado[:duracion] # Decidir siguiente paso if config[:siguiente] puts "\n➡️ Pasando a: #{config[:siguiente]}" paso_actual = config[:siguiente] else puts "\n✅ Pipeline completado exitosamente!" break end end # Resumen final puts "\n" + "="*70 puts "📊 RESUMEN DEL PIPELINE" puts "="*70 if contexto[:pipeline_completo] || contexto[:estado_integridad] == 'OK' puts " Estado: ✅ COMPLETADO" puts " Integridad BD: #{contexto[:estado_integridad] || 'N/A'}" else puts " Estado: ⏳ EN PROGRESO" end puts " Backup Drive: #{contexto[:drive_ruta] || contexto[:drive_origen] || 'N/A'}" puts " Duración total: #{duraciones.sum.round(2)}s (#{duraciones.count} drones)" puts "="*70 rescue => e puts "\n❌ Error en orquestador: #{e.message}" puts e.backtrace.first(5).join("\n") exit 1 end