CreaRack-SL

El restore aprende a actualizar: machaca lo que coincide, nunca borra (v1.75.0)

Valor para el usuario

Hasta ahora, restaurar un backup solo sabía “añadir”: todo lo que traía la maleta se recreaba junto a lo que ya había en la organización, así que restaurar dos veces el mismo backup duplicaba cada rack, cada cable, cada ficha. Desde v1.75.0 el motor del restore acepta un segundo modo, “Actualizar lo existente”: lo que coincide con el backup por su identidad natural (el nombre de un rack o plano, la IP de una ficha o de un target de monitorización, el título de un runbook, el device+puerto de un cable, el nombre de una playlist o proyecto de Signage…) se sobrescribe con los datos de la copia en vez de duplicarse, lo nuevo se añade igual que antes, y lo que vive en la organización y NO viene en el backup nunca se borra — actualizar no es sincronizar. Es la pieza pedida por Edu el día anterior (“avisar y actualizar si son los mismos datos” en vez de duplicar), decidida en el grill del 21-08 (opción A: el modo es siempre una elección explícita del usuario en cada restauración, nunca automático). Si al fusionar dos equipos acabaran solapados en el mismo hueco del rack, la restauración entera se rechaza con el motivo, sin dejar nada a medias. Segunda de las cuatro piezas de la ventana del task #236 — la cara visible (el selector de modo en el modal de restauración) llega en la siguiente pieza; hoy el motor queda listo pero aún no expuesto en la UI.

Cómo usarla

  1. El endpoint de restore acepta un parámetro mode ("add" por defecto, o "update"); si no es uno de esos dos valores responde 400.
  2. La tarea Huey run_full_restore(job_id, org_id, zip_path, rack_ids=None, mode="add") propaga el modo a apply_full_restore_from_zip() (ver [[entity—racks—service—apply-full-restore-from-zip]]) y guarda mode en el resultado del job junto a counts (lo creado) y el nuevo updated (lo machacado).
  3. En modo "update", cada modelo con identidad natural clara se busca primero (por nombre, IP, título…) y si existe se actualiza en vez de crearse; los devices se emparejan por su nombre dentro del mismo rack.
  4. Tras aplicar, la verificación esperado-vs-aplicado suma creado + actualizado: un re-restore perfecto en modo actualizar sale complete: true con 0 elementos creados.
  5. El selector visual del modo en el modal de restauración (reutilizando el árbol ya construido para elegir qué backupear, ver [[feature—backup-restore—creacion-selectiva-v1-74-0]]) llega en la siguiente pieza de la ventana.

Implementación

  • racks/api/export/restore_update.py (nuevo): dos helpers reutilizados por todo el restore — apply_fields(obj, fields) escribe solo los campos que de verdad cambiaron y únicamente guarda si hubo diferencia (evita saves y señales inútiles cuando el backup y la organización ya coinciden), y upsert(model, match, fields, counts, updated, key) busca por match y actualiza si existe o crea si no, incrementando el contador correspondiente.
  • racks/api/export/restore.py: apply_full_restore_from_zip() gana el parámetro mode: str = "add" y el diccionario updated (paralelo a counts, vacío en modo añadir). Las secciones de racks y planos —que hacían que el fichero superara las 500 LOC de lógica de la Regla 5— se movieron al nuevo racks/api/export/restore_inventory.py (268 LOC).
  • racks/api/export/restore_domains.py: restore_extended_domains() gana los mismos parámetros mode y updated; cada dominio (cables físicos, filas/salas DCIM, políticas SLA, canales de notificación, runbooks, ventanas de mantenimiento, playlists/schedules/proyectos de Signage…) define su propia identidad natural para el upsert. Detalles finos decididos con Edu: las anotaciones de un plano existente se conservan siempre (no hay clave natural para machacarlas sin arriesgar el dibujo del usuario), el status vivo de un SignagePlayer no se pisa (lo gobierna su telemetría, no el backup), los niveles de una EscalationPolicy se reemplazan enteros (son su configuración interna, no una lista a fusionar elemento a elemento), y los vínculos ficha↔device/target solo se machacan si el backup trae uno mapeado.
  • Validación todo-o-nada del resultado fusionado: tras aplicar el modo actualizar se revalida el rango y los solapes de U de cada rack sobre el estado FINAL (lo existente fusionado con el backup) — si dos dispositivos acaban en el mismo hueco, toda la transacción se rechaza con el motivo, igual que en un restore normal.
  • racks/tasks.py: run_full_restore() gana el parámetro mode="add" y lo propaga a apply_full_restore_from_zip; el resultado del job Huey incluye ahora updated y mode.
  • Tests: tests/api/test_restore_update.py (8 casos nuevos: re-restore sin duplicados, machaque real, nunca-borrar, solape de U rechazado, validación de la API). Área restore en Docker: 30 tests + mypy en verde; el modo añadir queda byte-idéntico (las suites previas no cambian).

Commits relacionados

  • 26baa9cc (2026-08-21) — feat(backups): modo Actualizar en el restore - machaca lo que coincide, nunca borra - v1.75.0 (task #236 ventana) (#406).

Véase también

  • [[entity—racks—service—apply-full-restore-from-zip]]
  • [[feature—racks—restore-selectivo-v171]]
  • [[feature—backup-restore—additive-restore-confirmation-v1-66-2]]
  • [[feature—backup-restore—extended-domains-v1-70-0]]
  • [[feature—backup-restore—creacion-selectiva-v1-74-0]]
  • [[feature—backup-restore—manifest-verificacion-v1-69-0]]
  • [[feature—racks—backup-restore-async-v156]]
  • [[crearack-tech—admin—backup-restore]]