diff --git a/adn/tools/bkps/bkps.rb b/adn/tools/bkps/bkps.rb index b33bcd1e..713e89c4 100644 --- a/adn/tools/bkps/bkps.rb +++ b/adn/tools/bkps/bkps.rb @@ -767,11 +767,13 @@ module ADN # ─── Orquestador: 1 dron por archivo ────────────────────────── def cmd_orquestar(args) - options = {} + options = { on_error: 'abort', retries: 2 } OptionParser.new do |opts| opts.banner = "Uso: ./adn/tools/run bkps orquestar [opciones]" opts.on("--timeout N", Integer, "Timeout por dron en segundos (default: 7200)") { |t| options[:timeout] = t } opts.on("--dry-run", "Solo listar archivos sin lanzar drones") { options[:dry_run] = true } + opts.on("--on-error MODE", %w[abort continue retry], "Qué hacer si un dron falla (abort|continue|retry, default: abort)") { |m| options[:on_error] = m } + opts.on("--retries N", Integer, "Número de reintentos si --on-error retry (default: 2)") { |n| options[:retries] = n } end.parse!(args) ref = args.shift @@ -813,6 +815,7 @@ module ADN puts " Total archivos: #{archivos.length}" puts " Pendientes: #{pendientes.length}" puts " Ya procesados: #{archivos.length - pendientes.length}" + puts " On-error: #{options[:on_error]}#{options[:on_error] == 'retry' ? " (max #{options[:retries]})" : ''}" puts "" if pendientes.empty? @@ -823,36 +826,75 @@ module ADN adn_run = File.join(ADN::PROJECT_ROOT, 'adn', 'tools', 'run') timeout = options[:timeout] || 7200 + # Tracking de resultados + resultados = [] + abortado = false + pendientes.each_with_index do |arch, i| tamaño_gb = (arch[:tamaño].to_f / 1024**3).round(1) puts " #{Color::BOLD}[#{i+1}/#{pendientes.length}]#{Color::RESET} #{arch[:vm]} — #{arch[:archivo]} (#{tamaño_gb}GB)" if options[:dry_run] puts " #{Color::YELLOW}[DRY-RUN] Lanzaría dron para: #{arch[:archivo]}#{Color::RESET}" + resultados << { vm: arch[:vm], estado: :skipped } next end - # Lanzar dron atómico para este archivo + # Lanzar dron atómico con reintentos nota = "Comprimir #{arch[:vm]} (#{tamaño_gb}GB)" cmd_dron = "#{adn_run} dron lanzar" \ " --nota \"#{nota}\"" \ " --timeout #{timeout}" \ " -- #{adn_run} bkps run #{ref} --batch --archivo #{Shellwords.escape(arch[:archivo])}" - puts " 🛸 Lanzando dron..." - resultado = system(cmd_dron) + max_intentos = options[:on_error] == 'retry' ? options[:retries] + 1 : 1 + exito = false - if resultado + max_intentos.times do |intento| + etiqueta = max_intentos > 1 ? " (intento #{intento + 1}/#{max_intentos})" : "" + puts " 🛸 Lanzando dron#{etiqueta}..." + exito = system(cmd_dron) + break if exito + + if intento < max_intentos - 1 + puts " #{Color::YELLOW}⟳ Reintentando...#{Color::RESET}" + end + end + + if exito puts " #{Color::GREEN}✔ Dron completado#{Color::RESET}" + resultados << { vm: arch[:vm], estado: :ok } else - puts " #{Color::RED}✖ Dron falló — deteniendo orquestación#{Color::RESET}" - break + resultados << { vm: arch[:vm], estado: :failed } + case options[:on_error] + when 'abort' + puts " #{Color::RED}✖ Dron falló — deteniendo orquestación#{Color::RESET}" + abortado = true + break + when 'continue' + puts " #{Color::YELLOW}⚠ Dron falló — continuando con el siguiente#{Color::RESET}" + when 'retry' + puts " #{Color::RED}✖ Dron falló después de #{max_intentos} intentos — deteniendo#{Color::RESET}" + abortado = true + break + end end puts "" end - puts "#{Color::BOLD}🐝 Orquestación finalizada.#{Color::RESET}" + # Resumen final + ok = resultados.count { |r| r[:estado] == :ok } + failed = resultados.count { |r| r[:estado] == :failed } + + puts "\n#{Color::BOLD}🐝 Resumen de Orquestación#{Color::RESET}" + puts "═" * 40 + puts " ✅ Exitosos: #{ok}" + puts " ❌ Fallidos: #{failed}" if failed > 0 + puts " ⏭️ Saltados: #{pendientes.length - ok - failed}" if abortado + puts "#{Color::BOLD}🐝 Orquestación #{abortado ? 'abortada' : 'finalizada'}.#{Color::RESET}" + + exit 1 if failed > 0 end def ejecutar_comando(comando, options = {})