Volver a la wiki

Servicio / Tarea Huey `apply_full_restore_from_zip()` — Restaurar desde ZIP (v1.56.0)

Signature

def apply_full_restore_from_zip(org, zip_path: str, rack_ids=None, mode: str = "add", tree_selection=None) -> dict:
    """Procesa un ZIP de backup y aplica las entidades bajo `org`.

    Devuelve {"counts": {...}, "updated": {...}, "verification": {...}} — lo
    creado, lo machacado (solo mode="update") y el informe esperado-vs-aplicado.
    Con `rack_ids` restaura SOLO esos racks (lista vacía incluida desde v1.73.0
    — task #236, PR #402); los dominios transversales viajan enteros.

    `mode` (v1.75.0, task #236 ventana): "add" (histórico) recrea todo junto a
    lo existente; "update" machaca por identidad natural lo que coincide, añade
    lo nuevo, y nunca borra lo que la organización tiene y el backup no trae.

    `tree_selection` (v1.76.0, task #236 ventana): selección de ÁRBOL del modal
    de restauración (mismo esquema que la creación selectiva, IDs de la maleta).
    Si no es None, PREVALECE sobre `rack_ids` — se poda con `filter_backup_by_tree()`
    en vez de `filter_backup_selection()`.
    """

Llamador: restore_full() endpoint (via Huey task run_full_restore).
Timeout: ~10-15 minutos.
Transacción: Todo-o-nada (rollback si error).

Descripción

Restaura ~13 modelos desde el backup_data.json extraído del ZIP. Si falla cualquier modelo, rollback completo. Responde con contadores (n_racks, n_devices, etc.).

Flujo

  1. Parse backup_data["organization"] → verifica que sea la org actual.
  2. Dentro de transaction.atomic():
    • Clear tablas (racks, devices, etc.).
    • Restaura en orden (org → groups → racks → devices → configs, etc.).
    • Crea relaciones FK.
  3. Si error en cualquier paso → rollback automático.
  4. Retorna {"racks": 50, "devices": 450, "success": true}.

DeviceProfile — restauración completa de la Ficha Central (v1.72.1, task #236, PR #400)

get_or_create() sobre organization + ip_address ahora restaura, en defaults, los campos que el serializador de backup dejaba fuera hasta v1.72.0: assigned_page, role, location, notes, manual_fields y las credenciales propias de la ficha (SNMP v3, SSH — cifradas, se restauran tal cual estaban en el ZIP). Ver el origen del fix en [[entity—racks—service—run-full-backup]].

SignagePlayer — deduplicación en restore repetido (v1.72.2, task #236, PR #401)

La restauración de los dominios extendidos (Signage, ITSM, DCIM…) vive en racks/api/export/restore_domains.py, función restore_extended_domains(), invocada desde apply_full_restore_from_zip() (restore.py:502) dentro de la misma transacción todo-o-nada.

SignagePlayer tiene unique_together (organization, device_profile) y su DeviceProfile asociado deduplica entre restores (ver sección anterior). Hasta v1.72.1, un segundo restore del mismo backup sobre la misma organización encontraba el DeviceProfile ya existente pero intentaba crear otro SignagePlayer sobre esa misma ficha → UniqueViolation en signage_signageplayer, que tumbaba la transacción entera (no solo el player) con el mensaje genérico “Restore failed”. Cazado por Edu en el ensayo (Ensayo01, 20-08-2026).

Fix: restore_extended_domains() ahora usa get_or_create(organization=org, device_profile_id=new_profile_id, defaults={...}) cuando el player tiene ficha de dispositivo vinculada. Si created=False (ya existía), no se tocan sus grupos ni se incrementa el contador — el informe de verificación del restore lo cuenta como already_existed_or_skipped. Sin ficha vinculada (device_profile_id nulo) no hay clave natural para deduplicar: el player se crea siempre, duplicando como el resto de dominios extendidos (comportamiento “restore ADDS”, ver [[feature—backup-restore—additive-restore-confirmation-v1-66-2]]).

Es el único modelo de los dominios extendidos con unicidad sobre una clave que además deduplica — por eso era el único que reventaba la transacción en un segundo restore; el resto de entidades de esos dominios no tiene esa combinación y simplemente duplica (ADDS).

Selección vacía también es un restore válido — flag selective + inventario completo en la UI (v1.73.0, task #236, PR #402)

Tras el ensayo, Edu pidió poder restaurar solo los dominios transversales (stencils, grupos, monitorización, Signage, ITSM) sin traer ningún rack — y ver antes de confirmar qué contiene realmente la maleta, no solo la lista de racks.

Ver el flujo completo (preview, selección, informe) en [[feature—racks—restore-selectivo-v171]].

Modo Actualizar — “machaca lo que coincide, nunca borra” (v1.75.0, task #236 ventana)

Junto al histórico modo “añadir” (todo se recrea, duplicando lo que ya existe), el restore acepta desde v1.75.0 un segundo modo explícito: mode="update". Decidido con Edu en el grill del 21-08 (opción A: el modo es SIEMPRE una elección global del usuario, nunca automático ni mezclado dentro de la misma restauración). Detalle de valor de usuario en [[feature—backup-restore—modo-actualizar-v1-75-0]]; aquí el resumen de implementación:

Selección por árbol en la restauración — tree_selection (v1.76.0, task #236 ventana)

Tercera pieza de la ventana: el parámetro rack_ids (lista plana de IDs) convive ahora con tree_selection, la selección de ÁRBOL que manda el modal de restauración nuevo — mismo esquema {blueprint_ids, include_nomap, domains} que la creación selectiva ([[feature—backup-restore—creacion-selectiva-v1-74-0]]), pero con los IDs referidos a la MALETA (no hay organización origen a mano para resolverlos). Si tree_selection no es None, prevalece sobre rack_ids, que queda como compat para clientes viejos.

Extracción de medios directa al org destino — incidente 20-08 (v1.77.2, task #236 ventana)

Cuarta pieza de la ventana, esta vez el fix de un incidente: detalle completo (síntomas, causa raíz, recuperación) en [[incident—20260820—restore-roba-medios-signage-org-origen]]. Restaurar en la MISMA instalación (org origen y destino comparten MEDIA_ROOT) con un backup SIN vídeos hacía que relocate_signage_files() moviera (os.replace) los ficheros ORIGINALES vivos del org origen — la función no distinguía “extraído del ZIP” de “ya existente en esa ruta”. Vació la biblioteca de Signage de una organización real durante un ensayo en la organización de pruebas; recuperado desde el ZIP de un backup anterior.

Aviso de enlaces de publicación nuevos en Signage — notices (v1.124.0, task #276)

Quinta pieza: restore_extended_domains() devuelve ahora un segundo valor, un dict notices, que apply_full_restore_from_zip() reenvía tal cual en la clave notices de su resultado, y que run_full_restore() (racks/tasks.py) copia al resultado del AsyncJob para que llegue al frontend.

Por qué: el publish_token de SignagePlayer se excluye del backup a propósito (ver más arriba, sección “Fuera a propósito” del valor de usuario de esta página), así que cada player CREADO por un restore estrena un token nuevo — el aparato físico (SpinetiX) sigue pidiendo la URL vieja hasta que alguien lo reconfigura a mano. Hasta esta pieza, ese cambio era silencioso: el CCIB estuvo 9 días con una pantalla sin actualizar tras un restore (ronda 30-08) sin que nadie supiera por qué.

Seguridad

Véase también

Subir