Servicio de Backup (core/services/backup_service.py)
Propósito
Gestión centralizada de backups por organización:
- Generación de ZIPs scoped (datos + uploads referenciados), verificados de integridad (task #236)
- Gestión de directorios de backup
- Limpieza de backups antiguos
- Búsqueda de último backup disponible
Funciones públicas
generate_backup(org: Organization) → str | None
Firma: generate_backup(org) -> str | None
Propósito: Genera un ZIP completo y VERIFICADO de una organización (datos + uploads). Devuelve None si la verificación de integridad falla — un backup que no se puede confiar es peor que uno visiblemente ausente.
Cambio v1.69.0 (task #236, PR-1): ya NO serializa los modelos inline. Delega en racks.api.export.backup.write_full_backup_zip() — el mismo punto único de generación que usa el backup manual — y verifica el resultado con core.services.backup_verify.verify_backup_zip().
Antes de v1.69.0: este módulo mantenía una copia propia del serializador (~230 líneas), que había derivado en silencio respecto al formato manual: faltaba script_templates, device_profiles recortados a 10 campos (de ~40), blueprints sin mode/sort_order, y credenciales SNMP en forma cifrada no restaurable. Nadie lo sabía porque nada lo comprobaba — el backup automático nocturno llevaba tiempo generando maletas incompletas.
Flujo actual:
write_full_backup_zip(org, zip_path)— escribe datos + uploads +manifest.json(import local: evita ciclo conracks.api.export.backup, que a su vez importa helpers de este módulo).verify_backup_zip(zip_path)— relee el ZIP de disco.- Si
verification["ok"]esFalse: loguea los errores, borra el ZIP si existe, devuelveNone. - Si es
True: loguea éxito, devuelve la ruta del ZIP.
Locación: core/services/backup_service.py línea ~92
Seguridad: Scoped por org (via write_full_backup_zip); solo empaqueta uploads referenciados (evita path escape)
get_backup_dir(org: Organization) → str
Propósito: Retorna (creando si falta) el directorio de backups de una org
Locación: core/services/backup_service.py línea ~80
cleanup_old_backups(org: Organization) → None
Propósito: Limpia backups más antiguos que N días (configurable)
Locación: core/services/backup_service.py (sin cambios en v1.69.0)
get_latest_backup(org: Organization) → str | None
Propósito: Obtiene la ruta del backup más reciente de una org
Locación: core/services/backup_service.py (sin cambios en v1.69.0)
Helpers de Scoping de Uploads (s107 · fix)
(sin cambios de comportamiento en v1.69.0 — viven en racks/api/export/backup.py, reusados por write_full_backup_zip(); ver [[entity—racks—service—run-full-backup]])
_media_relpath(image_path: str) → str | None
Normaliza variantes de rutas almacenadas a forma canónica uploads/.... Rechaza strings vacíos, paths fuera de uploads/ y paths no normalizables.
collect_org_upload_files(image_paths: list[str]) → list[tuple[str, str]]
Mapea paths almacenados → ficheros existentes en disco, scoped a los referenciados. Solo ficheros existentes, dedup, anti-escape (../../../etc/passwd bloqueado).
org_upload_image_paths(backup_data: dict) → list[str]
Extrae todos los image_path referenciados por una org a partir de su backup_data (stencils + blueprints).
Integración
Usado por:
racks/tasks.py::run_full_backup()— backup manual (Huey), víawrite_full_backup_zip(). Ver [[entity—racks—service—run-full-backup]].- Cron nocturno — backup automático, vía
generate_backup()(este módulo).
Dependencias (desde v1.69.0):
racks.api.export.backup.write_full_backup_zip()— punto único de generación del ZIP + manifest.core.services.backup_verify.verify_backup_zip()— verificación de integridad tras escribir. Ver [[entity—core—service—backup-verify]].- Django ORM (indirecto, vía
write_full_backup_zip) settings.MEDIA_ROOT(ubicación de uploads)
Cambio principal (s107)
Antes: os.walk(MEDIA_ROOT/uploads) → ZIP con TODOS los uploads (todos los tenants)
Después: collect_org_upload_files() → ZIP con uploads solo de la org
Impacto:
- Tamaño: 333 MB → 30-40 MB (rango esperado)
- Seguridad: cierre de fuga cross-tenant
- Compatibilidad: ZIP sigue formato
uploads/...(restore.py intacto)
Tests
Archivo: tests/api/test_backup.py (scoping de uploads) + tests/api/test_backup_verify.py (8 tests, integridad y paridad automático=manual, task #236)
TestMediaRelpathHelper: normalización de rutasTestCollectOrgUploadFiles: dedup, anti-escape, only-existingTestBackupTenantScoping::test_backup_excludes_other_org_uploads: cross-tenant scoping ✓- Paridad de formato: el backup automático y el manual producen el mismo
backup_data.json(task #236)
Véase también
- [[feature—racks—full-backup-tenant-scoping]]
- [[entity—racks—endpoint—backup-full]]
- [[entity—racks—model—stencil]]
- [[entity—racks—model—blueprint]]
- [[entity—core—model—organization]]
- [[entity—racks—api—restore]]
- [[entity—core—service—backup-verify]]
- [[feature—backup-restore—manifest-verificacion-v1-69-0]]