🐝 F2.T1c: Atomización rclone — 1 dron por directorio de nodo

- sync_rclone.rb: listar() + ejecutar_uno() implementados
- Soporta origen local (Dir.children) y remoto (rclone lsf)
- Orquestador: nota dinámica (Sync vs Comprimir según tipo)
- Validado dry-run: T4 (2 dirs), T5 (17 dirs), T6 (2 dirs)
- Legacy ejecutar() preservado para compatibilidad
This commit is contained in:
Ricardo Monla
2026-04-09 21:13:50 -03:00
parent 02404556cd
commit 6ebf1c3010
2 changed files with 107 additions and 3 deletions
+2 -1
View File
@@ -841,7 +841,8 @@ module ADN
end end
# Lanzar dron atómico con reintentos # Lanzar dron atómico con reintentos
nota = "Comprimir #{arch[:vm]} (#{tamaño_gb}GB)" verbo = tipo == 'rclone' ? '☁️ Sync' : 'Comprimir'
nota = "#{verbo} #{arch[:vm]} (#{tamaño_gb}GB)"
cmd_dron = "#{adn_run} dron lanzar" \ cmd_dron = "#{adn_run} dron lanzar" \
" --nota \"#{nota}\"" \ " --nota \"#{nota}\"" \
" --timeout #{timeout}" \ " --timeout #{timeout}" \
+105 -2
View File
@@ -3,6 +3,11 @@
# ========================================================== # ==========================================================
# adn/tools/bkps/lib/sync_rclone.rb — Sincronización rclone # adn/tools/bkps/lib/sync_rclone.rb — Sincronización rclone
# Migrado desde dtic-BKPs v6.0 → ADN::BKPs # Migrado desde dtic-BKPs v6.0 → ADN::BKPs
#
# Fase 2: Soporta orquestación 1-dron-por-directorio
# listar(tarea) → lista subdirectorios (nodos)
# ejecutar_uno(tarea, nodo) → sincroniza UN solo directorio
# ejecutar(tarea) → sincroniza TODO el origen (legacy)
# ========================================================== # ==========================================================
require 'shellwords' require 'shellwords'
@@ -26,6 +31,32 @@ module ADN
@log = log @log = log
end end
# Scanner: devuelve lista de subdirectorios (nodos) en el origen
# Compatible con el orquestador que espera {archivo:, vm:, tamaño:, pendiente:}
def listar(tarea)
origen = tarea[:origen]
# Determinar si el origen es local o remoto (rclone remote)
if origen.include?(':')
listar_remoto(tarea)
else
listar_local(tarea)
end
end
# Atómico: sincroniza UN solo directorio de nodo
def ejecutar_uno(tarea, nombre_directorio)
origen = File.join(tarea[:origen], nombre_directorio, '')
destino = File.join(tarea[:destino], nombre_directorio, '')
@log.info("☁️ Sync atómico: #{nombre_directorio}")
@log.info(" Origen: #{origen}")
@log.info(" Destino: #{destino}")
sincronizar(tarea, origen, destino)
end
# Legacy: sincroniza TODO el origen (compatibilidad con sistema previo)
def ejecutar(tarea) def ejecutar(tarea)
@log.info("--- #{tarea[:texto]} ---") @log.info("--- #{tarea[:texto]} ---")
origen = tarea[:origen] origen = tarea[:origen]
@@ -34,8 +65,72 @@ module ADN
@log.info("Origen: #{origen}") @log.info("Origen: #{origen}")
@log.info("Destino: #{destino}") @log.info("Destino: #{destino}")
sincronizar(tarea, origen, destino)
end
private
# ─── Listado local ──────────────────────────────────────────
def listar_local(tarea)
origen = tarea[:origen]
destino = tarea[:destino]
unless Dir.exist?(origen)
@log.error("Directorio origen no existe: #{origen}")
return []
end
Dir.children(origen)
.select { |d| File.directory?(File.join(origen, d)) }
.sort
.map do |nombre|
ruta_local = File.join(origen, nombre)
tamaño = calcular_tamaño_dir(ruta_local)
{
archivo: nombre, # Identificador para ejecutar_uno
vm: nombre, # Nombre visible (nodo)
ruta: ruta_local,
destino: File.join(destino, nombre, ''),
tamaño: tamaño,
pendiente: true # Siempre sincronizar (rclone decide deltas)
}
end
end
# ─── Listado remoto (rclone lsf) ────────────────────────────
def listar_remoto(tarea)
origen = tarea[:origen]
destino = tarea[:destino]
cmd = "rclone lsf #{Shellwords.escape(origen)} --dirs-only 2>/dev/null"
output = `#{cmd}`
unless $?.success?
@log.error("No se pudo listar remoto: #{origen}")
return []
end
output.split("\n")
.map { |d| d.chomp('/').strip }
.reject(&:empty?)
.sort
.map do |nombre|
{
archivo: nombre,
vm: nombre,
ruta: File.join(origen, nombre, ''),
destino: File.join(destino, nombre, ''),
tamaño: 0, # No calculamos tamaño remoto (costoso)
pendiente: true
}
end
end
# ─── Sincronización ─────────────────────────────────────────
def sincronizar(_tarea, origen, destino)
cmd = "rclone sync #{Shellwords.escape(origen)} #{Shellwords.escape(destino)} #{RCLONE_OPTS}" cmd = "rclone sync #{Shellwords.escape(origen)} #{Shellwords.escape(destino)} #{RCLONE_OPTS}"
@log.info("#{tarea[:id]}: Ejecutando → #{cmd}") @log.info("Ejecutando → #{cmd}")
exito = system(cmd) exito = system(cmd)
codigo = $?.exitstatus codigo = $?.exitstatus
@@ -43,11 +138,19 @@ module ADN
if exito if exito
@log.info("✔ Sincronización rclone completada.") @log.info("✔ Sincronización rclone completada.")
else else
@log.error("#{tarea[:id]}: Fallo en sincronización (código: #{codigo}).") @log.error("Fallo en sincronización (código: #{codigo}).")
end end
exito exito
end end
# ─── Helpers ────────────────────────────────────────────────
def calcular_tamaño_dir(ruta)
output = `du -sb #{Shellwords.escape(ruta)} 2>/dev/null`.strip
output.split("\t").first.to_i
rescue
0
end
end end
end end
end end