[A04.P005] DASUTEN: Pipeline refactorizado + Documentación + Informes
Refactorización del pipeline de backups DASUTEN: 1. Flujo simplificado (un solo destino): - Backup directo a X:\ (share srv-ns8) - Eliminado paso intermedio E:\BK_SQL\sysdasuten\ - Reducido de 3 ubicaciones a 2 (X: → /var/tmp/) 2. Módulo común DasuExecutor (lib/dasu_executor.rb): - Centraliza ejecución PowerShell en dasu-sql4 - Centraliza verificación de archivos en fenix - Elimina código repetido en 6 drones (-52% líneas) 3. Drones refactorizados: - dasuten_exportar_srvv-fenix.rb (FULL → X:) - dasuten_exportar_diferencial_srvv-fenix.rb (DIF → X:) - dasuten_transferir_srvv-fenix-srv-ns8.rb (simplificado) - dasuten_restaurar_dasu-sql4.rb (usa DasuExecutor) - dasuten_restaurar_diferencial_dasu-sql4.rb (usa DasuExecutor) - dasuten_download_drive-dasu-sql4.rb (usa DasuExecutor) - dasuten_verificar-integridad_dasu-sql4.rb (usa DasuExecutor) 4. Integración BKPs mantenida: - T8-T15: Tareas individuales - C7: dasuten_full (pipeline semanal) - C8: dasuten_diferencial (pipeline diario) 5. Documentación actualizada: - A04.P006_Backups-DASUTEN.md: flujo simplificado + módulo común - Lecciones aprendidas agregadas 6. Nuevos archivos: - Informes técnicos DASUTEN (docs/informes/2026-04_DASUTEN-migracion/) - A01.P011/P012: Documentación de drones y pipeline - proc_linux.rb, ops.rb: Utilitarios del ecosistema 7. Scripts legacy (para referencia): - orquestador_pipeline.rb, orquestador_pipeline_diferencial.rb - dasuten_upload_srv-ns8-drive.rb
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
# A01.P011 — Drones de Operaciones: Actualización Automatizada de OS Linux
|
||||
|
||||
> **Estado**: 🟢 Fase 2 en progreso
|
||||
> **Pertenece a**: [A01 — Ecosistema ADN](./A01_dtic-ADN.md)
|
||||
> **Relacionado**: [A01.P009 — Evolución Dron ADN](./A01.P009_Evolucion-Dron-ADN.md)
|
||||
> **Fecha**: 2026-04-13
|
||||
> **Responsable**: Lic. Ricardo MONLA
|
||||
> **Versión**: 3.0
|
||||
|
||||
---
|
||||
|
||||
## 📈 Progreso
|
||||
|
||||
```
|
||||
Fase 1: ██████████ 100% Parche de Emergencia (hotfix en bkps.rb) ✅
|
||||
Fase 2: ████████░░ 80% Desacoplamiento — `dron ops` + candados ✅
|
||||
Fase 2.5: ████████░░ 80% Estandarización de Fichas de Nodos 🔧
|
||||
Fase 3: ░░░░░░░░░░ 0% Robustez — Pre-flight, Smart Lock, Reboot Check
|
||||
Fase 4: █████░░░░░ 50% Anillos de Despliegue (--ring habilitado) 🔧
|
||||
Fase 5: ░░░░░░░░░░ 0% Observabilidad — Métricas por Nodo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Resumen Ejecutivo
|
||||
|
||||
Este plan define la evolución de la capacidad de **Operaciones de Infraestructura** dentro del ecosistema ADN, comenzando por la actualización automatizada de sistemas operativos Linux (`apt update/upgrade`) mediante AtomicDrones.
|
||||
|
||||
**Problema raíz:** La funcionalidad de actualizar nodos Linux fue injertada como un parche rápido dentro de la herramienta de backups (`bkps.rb` / `proc_linux.rb`), violando el principio de responsabilidad única y generando acoplamiento semántico incorrecto. Actualizar un OS **no es un backup**.
|
||||
|
||||
**Visión:** Un nuevo subcomando `dron ops` (operaciones) que reutilice la infraestructura existente de ADN (`NodosInfo`, `dron/ejecutor.rb`, Bitácora Web) para orquestar tareas de mantenimiento de infraestructura de forma atómica, visible y desacoplada del sistema de backups.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Objetivo
|
||||
|
||||
Crear un sistema de **Drones de Operaciones** dentro del ecosistema ADN que permita:
|
||||
1. Actualizar sistemas operativos Linux de forma automatizada, desatendida y atómica (1 dron = 1 nodo).
|
||||
2. Registrar cada operación individualmente en la Bitácora Web con trazabilidad completa.
|
||||
3. Reutilizar las herramientas ADN existentes (`NodosInfo`, `dron/ejecutor.rb`, `Dron::Base`) sin duplicar código.
|
||||
4. Desacoplar completamente esta funcionalidad del módulo de backups (`bkps.rb`).
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Hallazgos y Diagnóstico
|
||||
|
||||
### Auditoría del Estado Actual (2026-04-12)
|
||||
|
||||
| Hallazgo | Severidad | Detalle |
|
||||
|----------|:---------:|---------|
|
||||
| **Acoplamiento anómalo** | ✅ Resuelto | `proc_linux.rb` vivía en `bkps/lib/` — ahora existe `dron/ops.rb` independiente |
|
||||
| **Duplicación de lógica de nodos** | ✅ Resuelto | `ops.rb` reutiliza `NodosInfo.extraer_metadata()` |
|
||||
| **Orquestación vía bkps** | ✅ Resuelto | Comando nuevo: `dron ops update_os --target linux` |
|
||||
| **Bloqueo interactivo** | ✅ Resuelto | Corregido con `DEBIAN_FRONTEND=noninteractive` |
|
||||
| **Falta de candados** | 🟡 Media | Nodos con auth por password ahora usan `candados run` + `sshpass` |
|
||||
| **Fichas no estandarizadas** | 🔴 Alta | El parser de `NodosInfo` falla en ~40% de fichas por formato libre |
|
||||
|
||||
### Hallazgo Crítico: Fichas de Nodos No Estandarizadas (2026-04-13)
|
||||
|
||||
> **Impacto:** El módulo `NodosInfo` detecta usuarios SSH incorrectos ("Requiere", "El", "Configurado", "ssh") porque las fichas `.md` de nodos no siguen un formato uniforme. Cada nodo fue documentado con estructura distinta.
|
||||
|
||||
| Nodo | Campo SSH en ficha | Usuario detectado | Correcto |
|
||||
|------|-------------------|:-----------------:|:--------:|
|
||||
| srvv-dns | `**SSH**: ssh rmonla@10.0.10.2` | `ssh` → corregido a `rmonla` | ✅ (con fallback) |
|
||||
| srvv-docs | `**Acceso**: Requiere permisos docker` | `Requiere` → corregido a `root` | ⚠️ (fallback) |
|
||||
| srvv-sitio0 | `**Acceso**: El usuario rmonla...` | `El` → corregido a `root` | ⚠️ (fallback) |
|
||||
| srvv-nginx-rm | `\| **SSH** \| Puerto 7022, usuario root` | `ssh` → corregido a `root` | ⚠️ (fallback) |
|
||||
| srvv-nginx-rm | `\| **Bóveda** \| srvv-nginx-rm:root` | No detectado | ❌ falta formato |
|
||||
|
||||
**Causa raíz:** Las fichas `.md` son prosa libre sin campos obligatorios estandarizados. El parser necesita ~15 regex heurísticas para cubrir variantes, y aun así falla.
|
||||
|
||||
### Código Duplicado Eliminado (Fase 2)
|
||||
|
||||
| Función | Antes (proc_linux.rb) | Ahora (ops.rb) |
|
||||
|---------|:-------------------:|------------------------|
|
||||
| Detectar si es Linux | `es_nodo_linux?()` 40 líneas | `NodosInfo.detectar_so() == :linux` |
|
||||
| Extraer IP | `extraer_info_nodo()` regex | `NodosInfo.detectar_ip()` |
|
||||
| Extraer usuario SSH | regex custom | `NodosInfo.detectar_usuario()` + sanitización |
|
||||
| Auth por password | No soportado | `candados run` + `sshpass` via `clave_boveda` |
|
||||
|
||||
### Inventario de Flota Linux (13 nodos detectados)
|
||||
|
||||
| Nodo | IP | Rol | Host |
|
||||
|------|-----|-----|------|
|
||||
| srv-dasu | 10.0.10.X | Servidor físico | — |
|
||||
| srv-pmox1 | 10.0.10.201 | Hipervisor Proxmox | — |
|
||||
| srv-pmox2 | 10.0.10.202 | Hipervisor Proxmox | — |
|
||||
| srv-pmox3 | 10.0.10.203 | Hipervisor Proxmox | — |
|
||||
| srv-xen1 | 10.0.10.X | Hipervisor XenServer (legacy) | — |
|
||||
| srvv-data | 10.0.10.X | VM — Datos | srv-pmoxN |
|
||||
| srvv-dns | 10.0.10.2 | VM — DNS CoreDNS | srv-pmox1 |
|
||||
| srvv-docs | 10.0.10.X | VM — Documentación | srv-pmoxN |
|
||||
| srvv-dtic | 10.0.10.X | VM — Servicios DTIC | srv-pmoxN |
|
||||
| srvv-koha | 10.0.10.X | VM — Biblioteca Koha | srv-pmoxN |
|
||||
| srvv-sitio0 | 10.0.10.X | VM — Sitio principal | srv-pmoxN |
|
||||
| srvv-sitio2 | 10.0.10.X | VM — Sitio secundario | srv-pmoxN |
|
||||
| srvv-uptime | 10.0.10.X | VM — Monitoreo | srv-pmoxN |
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Fases de Implementación
|
||||
|
||||
### Fase 1: Parche de Emergencia (Hotfix en bkps.rb) — ✅ COMPLETA
|
||||
|
||||
**Objetivo:** Lograr que los drones de actualización Linux funcionen mínimamente, aunque acoplados al módulo de backups.
|
||||
|
||||
| ID | Tarea | Estado |
|
||||
|:--:|-------|:------:|
|
||||
| F1.T1 | Sustituir `apt` por `apt-get` con `DEBIAN_FRONTEND=noninteractive` | ✅ |
|
||||
| F1.T2 | Agregar `--force-confdef --force-confold` a DPKG | ✅ |
|
||||
| F1.T3 | Registrar tipo `linux` en `TIPOS` de `bkps.rb` | ✅ |
|
||||
| F1.T4 | Corregir `ejecutar_uno()` para leer ficha `.md` del nodo individual | ✅ |
|
||||
| F1.T5 | Instalar gema `net-ssh` en entorno de usuario | ✅ |
|
||||
| F1.T6 | Corregir verbo en nota de Bitácora: `🐧 Actualizar` | ✅ |
|
||||
|
||||
**Criterio de éxito:** ✅ `./adn/tools/run bkps orquestar update_linux_nodes --on-error continue` ejecuta 13 drones individuales visibles en Bitácora Web.
|
||||
|
||||
```
|
||||
Fase 1: ██████████ 100% ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fase 2: Desacoplamiento — Nuevo CLI `dron ops` — 🔧 EN PROGRESO
|
||||
|
||||
**Objetivo:** Extraer la lógica de operaciones de infraestructura fuera de `bkps.rb` y crear un subcomando propio dentro del sistema de Drones ADN.
|
||||
|
||||
**Principio:** Reutilizar, no reimplementar. El 80% del código necesario ya existe en ADN Core.
|
||||
|
||||
| ID | Tarea | Descripción | Estado |
|
||||
|:--:|-------|-------------|:------:|
|
||||
| F2.T1 | Crear `adn/tools/cli/dron/ops.rb` | Módulo de operaciones de infraestructura | ✅ |
|
||||
| F2.T2 | Integrar `NodosInfo` como scanner | Filtrado por OS, estado, validación de usuario SSH | ✅ |
|
||||
| F2.T3 | Conectar con `dron/ejecutor.rb` | Cada nodo = 1 dron ejecutor estándar (auto-bitácora) | ✅ |
|
||||
| F2.T4 | Registrar en `dispatcher.rb` | Case `'ops'` → `OpsComando.ejecutar(args)` + ayuda CLI | ✅ |
|
||||
| F2.T5 | Integrar `candados` para auth password | Nodos con `clave_boveda` usan `candados run` + `sshpass` | ✅ |
|
||||
| F2.T6 | Deprecar `proc_linux.rb` de bkps | Marcar como legacy, redirigir a nuevo módulo | ⏳ |
|
||||
| F2.T7 | Eliminar `update_linux_nodes` de `bkps.yml` | Limpieza de la configuración de backups | ⏳ |
|
||||
|
||||
**Arquitectura implementada:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ./adn/tools/run dron ops update_os --target linux │
|
||||
└───────────────────────────┬─────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Dron::Ops (cli/dron/ops.rb) │
|
||||
│ │
|
||||
│ 1. Scanner: NodosInfo.extraer_metadata(nodo) para cada *.md │
|
||||
│ → filtra por OS explícito (Debian/Ubuntu/Proxmox) │
|
||||
│ → excluye Windows, firmware, cámaras │
|
||||
│ → excluye nodos con estado 🔴 │
|
||||
│ → sanitiza usuario SSH (lista negra + fallback user@IP) │
|
||||
│ │
|
||||
│ 2. Por cada nodo Linux: │
|
||||
│ → Si tiene clave_boveda: │
|
||||
│ candados run <clave> 'sshpass -p $PASS ssh ...' │
|
||||
│ → Si no: │
|
||||
│ ssh -i <key> user@ip 'apt-get update && upgrade' │
|
||||
│ → Dron::Ejecutor.lanzar(cmd, nota) → Bitácora Web │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Uso actual (funcional):**
|
||||
|
||||
```bash
|
||||
# Listar nodos Linux con tipo de auth (dry-run)
|
||||
./adn/tools/run dron ops update_os --target linux --dry-run
|
||||
|
||||
# Ejecutar con continuación ante fallos
|
||||
./adn/tools/run dron ops update_os --target linux --on-error continue
|
||||
|
||||
# Ejecutar solo un nodo específico
|
||||
./adn/tools/run dron ops update_os --nodo srvv-dns
|
||||
|
||||
# Ayuda completa
|
||||
./adn/tools/run dron ops --help
|
||||
```
|
||||
|
||||
**Criterio de éxito:** ⚠️ Parcial. El comando funciona y los drones son visibles en Bitácora. Pendiente deprecar `proc_linux.rb` y limpiar `bkps.yml` una vez estandarizadas las fichas.
|
||||
|
||||
```
|
||||
Fase 2: ████████░░ 80%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fase 2.5: Estandarización de Fichas de Nodos — 🔧 EN PROGRESO
|
||||
|
||||
**Objetivo:** Definir un formato canónico obligatorio para las fichas `nodos/*.md` que elimine la ambigüedad del parser `NodosInfo` y habilite operaciones confiables de toda herramienta ADN.
|
||||
|
||||
| ID | Tarea | Descripción | Estado |
|
||||
|:--:|-------|-------------|:------:|
|
||||
| F2.5.T1 | Definir formato canónico | Tabla `<!-- META:BEGIN -->` / `<!-- META:END -->` con campos fijos | ✅ |
|
||||
| F2.5.T2 | Migrar fichas existentes | 32/32 fichas migradas (14 Linux + 2 otros + 16 Windows) | ✅ |
|
||||
| F2.5.T3 | Actualizar `NodosInfo` | Parser dual: tabla canónica primero, regex fallback | ✅ |
|
||||
| F2.5.T4 | Simplificar `ops.rb` | Eliminadas ~30 líneas de sanitización y regex de contenido | ✅ |
|
||||
| F2.5.T5 | Validador de fichas | `./adn/tools/run nodos validar` → reporta fichas no conformes | ⏳ |
|
||||
| F2.5.T6 | Actualizar `generar nodo` | Que la plantilla del generador use el formato canónico | ⏳ |
|
||||
|
||||
**Formato canónico implementado:**
|
||||
|
||||
```markdown
|
||||
# Nodo: <hostname>
|
||||
|
||||
<!-- META:BEGIN -->
|
||||
| Campo | Valor |
|
||||
|:------|:------|
|
||||
| **Hostname** | `<hostname>` |
|
||||
| **IP** | `<ip>` |
|
||||
| **OS** | Debian 12 (Bookworm) |
|
||||
| **Estado** | 🟢 Online |
|
||||
| **Rol** | Descripción breve |
|
||||
| **SSH** | `user@ip:port` |
|
||||
| **Auth** | `key` / `password` / `rsa_legacy` |
|
||||
| **Bóveda** | `clave:candados` (solo si auth=password) |
|
||||
| **Host** | `host-anfitrión` (solo VMs) |
|
||||
| **VMID** | `123` (solo VMs) |
|
||||
| **Ring** | `test` / `standard` / `core` |
|
||||
<!-- META:END -->
|
||||
```
|
||||
|
||||
**Resultados:**
|
||||
- 32/32 fichas migradas con tabla canónica
|
||||
- `NodosInfo` lee tabla canónica en 1 regex (vs ~15 antes)
|
||||
- `ops.rb` simplificado: de ~65 líneas de scanner a ~20
|
||||
- Nodos Linux detectados: 14 → 17 (se descubrieron `dtic-bitacoras`, `srv-ns8`, `srvv-sitio`)
|
||||
- `--ring test|standard|core` habilitado en `dron ops` (Fase 4 parcial)
|
||||
|
||||
```
|
||||
Fase 2.5: ████████░░ 80%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fase 3: Robustez — Pre-flight, Smart Lock, Reboot Check
|
||||
|
||||
**Objetivo:** Hacer que cada dron de actualización sea inteligente: que verifique condiciones previas, detecte errores conocidos y reporte el estado post-actualización.
|
||||
|
||||
| ID | Tarea | Descripción | Estado |
|
||||
|:--:|-------|-------------|:------:|
|
||||
| F3.T1 | Pre-flight: espacio en disco | Abortar si `/` o `/var` están al >95% | ⏳ |
|
||||
| F3.T2 | Pre-flight: estado del nodo | Saltar nodos con `**Estado**: 🔴` | ⏳ |
|
||||
| F3.T3 | Smart Lock detection | Detectar `Could not get lock /var/lib/dpkg/lock` y reportar `❌ dpkg bloqueado` | ⏳ |
|
||||
| F3.T4 | Reboot Required check | Post-upgrade: `[ -f /var/run/reboot-required ]` → `⚠️ Reboot needed` | ⏳ |
|
||||
| F3.T5 | Resumen de paquetes actualizados | Capturar cantidad de paquetes actualizados del output de `apt-get` | ⏳ |
|
||||
|
||||
**Flujo enriquecido por nodo:**
|
||||
|
||||
```
|
||||
🐧 Dron update_os para srvv-dns
|
||||
├── 🔍 Pre-flight checks
|
||||
│ ├── Disco /: 61% ✔
|
||||
│ ├── Disco /var: OK ✔
|
||||
│ └── Estado: 🟢 ✔
|
||||
├── [1/3] apt-get update ✔
|
||||
├── [2/3] apt-get upgrade -y ✔ (4 paquetes)
|
||||
├── [3/3] apt-get autoremove -y ✔
|
||||
├── 🔍 Post-flight checks
|
||||
│ └── reboot-required: NO ✔
|
||||
└── ✅ srvv-dns actualizado (47s)
|
||||
```
|
||||
|
||||
**Criterio de éxito:** Cero drones con errores silenciosos. Cada falencia conocida tiene su propio código de diagnóstico en la nota de Bitácora.
|
||||
|
||||
```
|
||||
Fase 3: ░░░░░░░░░░ 0%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fase 4: Anillos de Despliegue (Rings)
|
||||
|
||||
**Objetivo:** No actualizar toda la flota de golpe. Dividir los nodos en anillos de criticidad para evitar caídas simultáneas de servicios dependientes.
|
||||
|
||||
| ID | Tarea | Descripción | Estado |
|
||||
|:--:|-------|-------------|:------:|
|
||||
| F4.T1 | Definir anillos en `nodos/*.md` | Nuevo campo `**Ring**: test|standard|core` | ⏳ |
|
||||
| F4.T2 | Ring 0 — Test | Nodos de bajo impacto: srvv-uptime, srvv-sitio2 | ⏳ |
|
||||
| F4.T3 | Ring 1 — Standard | VMs de servicios: srvv-dns, srvv-docs, srvv-koha | ⏳ |
|
||||
| F4.T4 | Ring 2 — Core | Hipervisores y data: srv-pmox1/2/3, srvv-data | ⏳ |
|
||||
| F4.T5 | Pausa entre anillos | Esperar confirmación o timeout entre rings | ⏳ |
|
||||
|
||||
**Uso propuesto:**
|
||||
|
||||
```bash
|
||||
# Solo nodos de testing
|
||||
./adn/tools/run dron ops update_os --ring test
|
||||
|
||||
# Despliegue completo con pausa entre anillos
|
||||
./adn/tools/run dron ops update_os --rings all --pause 300
|
||||
```
|
||||
|
||||
**Criterio de éxito:** Nunca se actualizan los 3 hipervisores Proxmox al mismo tiempo. Los nodos de servicio crítico se actualizan después de validar en el ring de test.
|
||||
|
||||
```
|
||||
Fase 4: ░░░░░░░░░░ 0%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fase 5: Observabilidad — Métricas por Nodo
|
||||
|
||||
**Objetivo:** Historial de actualizaciones por nodo visible en la Bitácora Web.
|
||||
|
||||
| ID | Tarea | Descripción | Estado |
|
||||
|:--:|-------|-------------|:------:|
|
||||
| F5.T1 | Registro de versión de OS | Capturar `lsb_release -d` pre y post-update | ⏳ |
|
||||
| F5.T2 | Historial de updates por nodo | Consulta: "¿Cuándo fue la última actualización de srvv-dns?" | ⏳ |
|
||||
| F5.T3 | Alerta de nodos atrasados | `dron ops status` → nodos sin update en >30 días | ⏳ |
|
||||
| F5.T4 | Dashboard de salud de flota OS | Vista consolidada en Bitácora Web | ⏳ |
|
||||
|
||||
**Criterio de éxito:** Con un solo comando se puede ver el estado de actualización de toda la flota Linux, detectando nodos atrasados.
|
||||
|
||||
```
|
||||
Fase 5: ░░░░░░░░░░ 0%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Conclusiones
|
||||
|
||||
### Lo que funciona hoy (Fases 1 + 2 + 2.5)
|
||||
- **17 nodos Linux** detectados correctamente (incluidos 3 Proxmox, 1 XenServer, srv-ns8).
|
||||
- **Desacoplamiento logrado:** `dron ops update_os` es subcomando propio del sistema de Drones ADN.
|
||||
- **Candados integrado:** `srvv-nginx-rm` y futuros nodos con password usan la bóveda.
|
||||
- **Fichas estandarizadas:** 32/32 con tabla `META:BEGIN` / `META:END`.
|
||||
- **Rings habilitados:** `--ring test|standard|core` filtra la flota.
|
||||
- **Dry-run informativo:** Muestra nodo, IP, usuario, auth type y ring.
|
||||
|
||||
### Desbloqueado por la estandarización
|
||||
- **Parser simplificado:** `NodosInfo` lee tabla canónica con 1 regex (antes ~15).
|
||||
- **Scanner limpio:** `ops.rb` pasó de ~65 líneas de workarounds a ~20 líneas claras.
|
||||
- **Nodos descubiertos:** 3 nodos Linux que antes no se detectaban (`dtic-bitacoras`, `srv-ns8`, `srvv-sitio`).
|
||||
- **Cero falsos positivos:** Cámaras IP, firmware y Windows correctamente excluidos.
|
||||
|
||||
### Métricas de referencia
|
||||
|
||||
| Métrica | Antes | Ahora |
|
||||
|---------|:-----:|:-----:|
|
||||
| Nodos Linux detectados | 14 | **17** |
|
||||
| Nodos Windows (excluidos) | 15 | 14 |
|
||||
| Total fichas en `nodos/` | 32 | 32 |
|
||||
| Fichas con tabla canónica | 0 | **32** (100%) |
|
||||
| Fichas con parseo regex (fallback) | 32 | **0** |
|
||||
| Regex en NodosInfo (operativas) | ~15 | **1** (canónica) |
|
||||
| Nodos con auth `key` | 13 | **16** |
|
||||
| Nodos con auth `candados` | 0 | **1** (srvv-nginx-rm) |
|
||||
| Líneas de scanner en `ops.rb` | ~65 | **~20** |
|
||||
| Falsos positivos en dry-run | ~2 | **0** |
|
||||
|
||||
---
|
||||
|
||||
## 📎 Referencias
|
||||
|
||||
- [A01.P009 — Evolución Dron ADN](./A01.P009_Evolucion-Dron-ADN.md) — Plan maestro de AtomicDrones
|
||||
- `adn/tools/cli/dron/ops.rb` — **Módulo nuevo de operaciones** (Fase 2, activo)
|
||||
- `adn/tools/cli/dron/dispatcher.rb` — Dispatcher con `ops` registrado
|
||||
- `adn/tools/cli/dron/ejecutor.rb` — Ejecutor atómico con auto-bitácora
|
||||
- `adn/tools/cli/nodos/info.rb` — Módulo `NodosInfo` (scanner de fichas)
|
||||
- `adn/tools/candados/candados.rb` — Bóveda de credenciales
|
||||
- `adn/tools/bkps/lib/proc_linux.rb` — Implementación legacy (Fase 1, a deprecar)
|
||||
|
||||
---
|
||||
|
||||
*Plan actualizado 2026-04-13 — Versión 3.0*
|
||||
@@ -0,0 +1,640 @@
|
||||
# A01.P012 — Pipeline de Drones para Backup y Restauración de DASUTEN
|
||||
|
||||
> **Estado**: 🟢 Pipeline implementado (2026-04-14)
|
||||
> **Pertenece a**: [A01 — Ecosistema ADN](./A01_dtic-ADN.md)
|
||||
> **Relacionado**: [A01.P009 — Evolución Dron ADN](./A01.P009_Evolucion-Dron-ADN.md)
|
||||
> **Caso de Uso**: [A04.P005 — DASUTEN sin DC](../dtic-DASUTEN/A04.P005_DASUTEN-sin-DC.md)
|
||||
> **Fecha**: 2026-04-14
|
||||
> **Responsable**: Lic. Ricardo MONLA
|
||||
> **Versión**: 2.0 — Con verificación de consistencia (DBCC CHECKDB) de BD
|
||||
|
||||
---
|
||||
|
||||
## 📈 Progreso
|
||||
|
||||
```
|
||||
Fase 0: ██████████ 100% Diseño de Arquitectura de Pipeline ✅
|
||||
Fase 1: ██████████ 100% Drones Atómicos Fragmentados ✅
|
||||
Fase 1.5: ██████████ 100% Orquestador con Intercomunicación JSON ✅
|
||||
Fase 1.7: ██████████ 100% Implementación Real — Pipeline DASUTEN ✅ (2026-04-14)
|
||||
Fase 1.8: ██████████ 100% Verificación de Consistencia (DBCC CHECKDB) ✅
|
||||
Fase 1.9: ██████████ 100% Pipeline Diferencial (Refresco diario) ✅
|
||||
Fase 2: ░░░░░░░░░░ 0% Persistencia en Bitácora — Outputs en DB
|
||||
Fase 3: ░░░░░░░░░░ 0% Reintentos Automáticos — Retry con backoff
|
||||
Fase 4: ░░░░░░░░░░ 0% Condicionales — Skip si output indica "ya hecho"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Resumen Ejecutivo
|
||||
|
||||
Este plan documenta el **Pipeline de Drones para Backup y Restauración de DASUTEN** — una implementación real donde múltiples drones atómicos se encadenan para formar el flujo de refresco de base de datos (Fase 8b), comunicándose entre sí mediante **archivos JSON** que actúan como medio de intercambio de contexto.
|
||||
|
||||
**Caso de uso:** Refresco de la base de datos `sysdasuten` desde `srvv-fenix` (producción) hasta `dasu-sql4` (nuevo servidor), pasando por Google Drive como intermediario.
|
||||
|
||||
**Problema resuelto:** Transferir 1.3 GB de backup entre servidores sin dependencia de Tailscale inestable, usando Google Drive como puente y verificando integridad con DBCC CHECKDB.
|
||||
|
||||
**Solución:** Un **orquestador** que:
|
||||
1. Lanza 6 drones atómicos secuencialmente
|
||||
2. Lee el output JSON de cada dron completado
|
||||
3. Inyecta el contexto en el siguiente dron
|
||||
4. Decide continuar o abortar según el resultado
|
||||
|
||||
### Convención de Nombres (desde 2026-04-14)
|
||||
|
||||
```
|
||||
dasuten_<verbo>_<origen-destino>.rb
|
||||
|
||||
Ejemplos:
|
||||
- dasuten_exportar_srvv-fenix.rb (acción en nodo específico)
|
||||
- dasuten_transferir_srvv-fenix-srv-ns8.rb (transferencia entre nodos)
|
||||
- dasuten_upload_srv-ns8-drive.rb (upload desde nodo a Drive)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Objetivo
|
||||
|
||||
Implementar un **Pipeline de Drones para el refresco de BD de DASUTEN** que permita:
|
||||
|
||||
1. **Fragmentar el flujo de backup** en 6 drones atómicos independientes (1 dron = 1 paso)
|
||||
2. **Intercomunicar drones** mediante archivos JSON estandarizados (`/tmp/dron_*_output.json`)
|
||||
3. **Orquestar el refresco completo** con un solo comando desde `srv-ns8`
|
||||
4. **Registrar trazabilidad** de cada paso en la Bitácora Web
|
||||
5. **Permitir re-ejecución** de pasos individuales sin re-correr todo el pipeline
|
||||
6. **Verificar integridad** de la BD restaurada con `DBCC CHECKDB`
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Arquitectura del Pipeline
|
||||
|
||||
### Diagrama de Flujo (6 Drones Atómicos)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ ORQUESTADOR (orquestador_pipeline.rb) │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Contexto Compartido (Hash Ruby) │ │
|
||||
│ │ - backup_ruta: ruta del archivo .bak │ │
|
||||
│ │ - drive_ruta: URL de Google Drive │ │
|
||||
│ │ - estado_integridad: resultado de DBCC CHECKDB │ │
|
||||
│ │ - duracion_total: suma de duraciones de drones │ │
|
||||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 1. Lanza dron
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ DRON: │ Nodo: srvv-fenix
|
||||
│ exportar │ Tarea: BACKUP DATABASE WITH COMPRESSION
|
||||
│ │ Output: /tmp/dron_export_output.json
|
||||
└─────────────────┘
|
||||
│
|
||||
│ 2. Orquestador lee JSON, extrae backup_ruta
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ DRON: │ Nodo: srv-ns8
|
||||
│ transferir │ Tarea: smbclient //fenix/BK_SQL → /var/tmp/
|
||||
│ │ Output: /tmp/dron_transfer_output.json
|
||||
└─────────────────┘
|
||||
│
|
||||
│ 3. Orquestador lee JSON, extrae backup_local
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ DRON: │ Nodo: srv-ns8
|
||||
│ upload │ Tarea: rclone copy → Google Drive
|
||||
│ │ Output: /tmp/dron_upload_output.json
|
||||
└─────────────────┘
|
||||
│
|
||||
│ 4. Orquestador lee JSON, extrae drive_ruta
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ DRON: │ Nodo: dasu-sql4
|
||||
│ download │ Tarea: Invoke-WebRequest ← Google Drive
|
||||
│ │ Output: /tmp/dron_download_output.json
|
||||
└─────────────────┘
|
||||
│
|
||||
│ 5. Orquestador lee JSON, extrae backup_local
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ DRON: │ Nodo: dasu-sql4
|
||||
│ restaurar │ Tarea: RESTORE DATABASE (sin CHECKDB)
|
||||
│ │ Output: /tmp/dron_restore_output.json
|
||||
└─────────────────┘
|
||||
│
|
||||
│ 6. Orquestador lee JSON, decide verificar
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ DRON: │ Nodo: dasu-sql4
|
||||
│ verificar │ Tarea: DBCC CHECKDB + validaciones
|
||||
│ │ Output: /tmp/dron_verify_output.json
|
||||
└─────────────────┘
|
||||
│
|
||||
▼
|
||||
✅ PIPELINE COMPLETADO (6 drones)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Drones Atómicos del Pipeline (6 drones)
|
||||
|
||||
Cada dron es **independiente**, **autocontenido** y **auto-bitácorado**.
|
||||
|
||||
### Dron 1: `dasuten_exportar_srvv-fenix.rb`
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Nodo** | `srvv-fenix` |
|
||||
| **Tarea** | Generar backup comprimido en SQL Server |
|
||||
| **Comando** | `BACKUP DATABASE sysdasuten TO DISK = ... WITH COMPRESSION` |
|
||||
| **Output File** | `/tmp/dron_export_output.json` |
|
||||
| **Output Clave** | `backup_ruta` |
|
||||
|
||||
**Output JSON:**
|
||||
```json
|
||||
{
|
||||
"paso": "export_complete",
|
||||
"backup_ruta": "E:\\BK_SQL\\sysdasuten_compressed_ADN.bak",
|
||||
"nodo": "srvv-fenix",
|
||||
"duracion": 45.2,
|
||||
"timestamp": "2026-04-14T10:00:00-03:00",
|
||||
"siguiente_paso": "transferir"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Dron 2: `dasuten_transferir_srvv-fenix-srv-ns8.rb`
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Nodo** | `srv-ns8` |
|
||||
| **Tarea** | Descargar backup desde srvv-fenix vía SMB |
|
||||
| **Comando** | `smbclient //10.0.10.200/BK_SQL -c "get sysdasuten_compressed_ADN.bak"` |
|
||||
| **Output File** | `/tmp/dron_transfer_output.json` |
|
||||
| **Output Clave** | `backup_local` |
|
||||
|
||||
**Output JSON:**
|
||||
```json
|
||||
{
|
||||
"paso": "transfer_complete",
|
||||
"backup_local": "/var/tmp/sysdasuten_compressed_ADN.bak",
|
||||
"nodo": "srv-ns8",
|
||||
"duracion": 120.5,
|
||||
"timestamp": "2026-04-14T10:02:00-03:00",
|
||||
"siguiente_paso": "upload"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Dron 3: `dasuten_upload_srv-ns8-drive.rb`
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Nodo** | `srv-ns8` |
|
||||
| **Tarea** | Subir backup a Google Drive vía rclone |
|
||||
| **Comando** | `rclone copy /var/tmp/... rmonla-GDrive:drive_bkps-dasu/...` |
|
||||
| **Output File** | `/tmp/dron_upload_output.json` |
|
||||
| **Output Clave** | `drive_ruta` |
|
||||
|
||||
**Output JSON:**
|
||||
```json
|
||||
{
|
||||
"paso": "upload_complete",
|
||||
"drive_ruta": "rmonla-GDrive:drive_bkps-dasu/sysdasuten_compressed_ADN_20260414_100500.bak",
|
||||
"drive_url": "https://drive.google.com/drive/folders/1Tgrn2QYyWf0v0eyY1bSGCLlhiqhtIA2n",
|
||||
"nodo": "srv-ns8",
|
||||
"duracion": 180.5,
|
||||
"timestamp": "2026-04-14T10:05:00-03:00",
|
||||
"siguiente_paso": "download"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Dron 4: `dasuten_download_drive-dasu-sql4.rb`
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Nodo** | `dasu-sql4` |
|
||||
| **Tarea** | Descargar backup desde Google Drive vía HTTP |
|
||||
| **Comando** | `Invoke-WebRequest -Uri "https://drive.usercontent.google.com/download?id=..."` |
|
||||
| **Output File** | `/tmp/dron_download_output.json` |
|
||||
| **Output Clave** | `backup_local` |
|
||||
|
||||
**Output JSON:**
|
||||
```json
|
||||
{
|
||||
"paso": "download_complete",
|
||||
"backup_local": "F:\\BACKUP\\sysdasuten_compressed_ADN.bak",
|
||||
"drive_origen": "rmonla-GDrive:drive_bkps-dasu/sysdasuten_compressed_ADN_20260414_100500.bak",
|
||||
"nodo": "dasu-sql4",
|
||||
"duracion": 150.3,
|
||||
"timestamp": "2026-04-14T10:08:00-03:00",
|
||||
"siguiente_paso": "restaurar_backup"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Dron 5: `dasuten_restaurar_dasu-sql4.rb`
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Nodo** | `dasu-sql4` |
|
||||
| **Tarea** | Restaurar backup (solo RESTORE, sin CHECKDB) |
|
||||
| **Comando** | `RESTORE DATABASE ... WITH REPLACE, MOVE ...` |
|
||||
| **Output File** | `/tmp/dron_restore_output.json` |
|
||||
| **Output Clave** | `estado_integridad=PENDING_CHECKDB` |
|
||||
|
||||
**Output JSON:**
|
||||
```json
|
||||
{
|
||||
"paso": "restore_complete",
|
||||
"backup_ruta": "F:\\BACKUP\\sysdasuten_compressed_ADN.bak",
|
||||
"nodo": "dasu-sql4",
|
||||
"duracion_restore": 0.23,
|
||||
"estado_integridad": "PENDING_CHECKDB",
|
||||
"timestamp": "2026-04-14T10:10:00-03:00",
|
||||
"siguiente_paso": "verificar_integridad"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Dron 6: `dasuten_verificar-integridad_dasu-sql4.rb`
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Nodo** | `dasu-sql4` |
|
||||
| **Tarea** | Verificar integridad con DBCC CHECKDB |
|
||||
| **Comando** | `DBCC CHECKDB('sysdasuten') WITH NO_INFOMSGS, ALL_ERRORMSGS` |
|
||||
| **Output File** | `/tmp/dron_verify_output.json` |
|
||||
| **Output Clave** | `estado_integridad`, `pipeline_completo` |
|
||||
|
||||
**Output JSON:**
|
||||
```json
|
||||
{
|
||||
"paso": "verify_complete",
|
||||
"nodo": "dasu-sql4",
|
||||
"duracion_checkdb": 248.56,
|
||||
"estado_integridad": "OK",
|
||||
"paginas_verificadas": 1186793,
|
||||
"timestamp": "2026-04-14T10:14:00-03:00",
|
||||
"pipeline_completo": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎼 Orquestador: `orquestador_pipeline.rb`
|
||||
|
||||
El orquestador es el **cerebro** del pipeline. No ejecuta tareas directamente, sino que:
|
||||
|
||||
1. **Lanza drones** secuencialmente vía `dron lanzar`
|
||||
2. **Lee outputs JSON** de cada dron completado
|
||||
3. **Inyecta contexto** en el siguiente dron (vía ENV o argumentos)
|
||||
4. **Decide continuar** o abortar según exito/fracaso
|
||||
5. **Registra estado** en la Bitácora Web
|
||||
|
||||
### Código del Orquestador (simplificado)
|
||||
|
||||
```ruby
|
||||
#!/usr/bin/env ruby
|
||||
# adn/tools/cli/drones/orquestador_pipeline.rb
|
||||
|
||||
ESTADOS = {
|
||||
exportar: {
|
||||
script: 'dasuten_exportar.rb',
|
||||
nodo: 'srvv-fenix',
|
||||
nota: '📤 Exportar backup DASUTEN',
|
||||
output_file: '/tmp/dron_export_output.json',
|
||||
siguiente: :upload
|
||||
},
|
||||
upload: {
|
||||
script: 'dasuten_upload_drive.rb',
|
||||
nodo: 'srv-ns8',
|
||||
nota: '☁️ Upload a Google Drive',
|
||||
output_file: '/tmp/dron_upload_output.json',
|
||||
siguiente: :download
|
||||
},
|
||||
# ... más pasos
|
||||
}.freeze
|
||||
|
||||
def lanzar_dron(script, nodo, nota, timeout)
|
||||
cmd = "#{ADN_RUN} dron lanzar --nota #{nota} --nodo #{nodo} --timeout #{timeout} -- ruby #{script}"
|
||||
exito = system(cmd)
|
||||
|
||||
if exito
|
||||
# Leer output del dron
|
||||
output_file = ESTADOS[nodo.to_sym][:output_file]
|
||||
resultado = JSON.parse(File.read(output_file)) if File.exist?(output_file)
|
||||
return true, resultado
|
||||
else
|
||||
return false, {}
|
||||
end
|
||||
end
|
||||
|
||||
# Ejecutar pipeline
|
||||
paso_actual = :exportar
|
||||
contexto = {}
|
||||
|
||||
loop do
|
||||
break unless ESTADOS[paso_actual]
|
||||
|
||||
config = ESTADOS[paso_actual]
|
||||
exito, resultado = lanzar_dron(config[:script], config[:nodo], config[:nota], timeout)
|
||||
|
||||
unless exito
|
||||
puts "❌ Pipeline fallido en paso: #{paso_actual}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
contexto.merge!(resultado)
|
||||
|
||||
if config[:siguiente]
|
||||
paso_actual = config[:siguiente]
|
||||
else
|
||||
puts "✅ Pipeline completado!"
|
||||
break
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Uso del Pipeline
|
||||
|
||||
### Ejecutar Pipeline Completo
|
||||
|
||||
```bash
|
||||
# Pipeline completo (todos los pasos encadenados)
|
||||
./adn/tools/run dron lanzar --nota "Pipeline DASUTEN 8b" -- \
|
||||
ruby adn/tools/cli/drones/orquestador_pipeline.rb
|
||||
```
|
||||
|
||||
### Ejecutar Drones Individualmente
|
||||
|
||||
```bash
|
||||
# Paso 1: Exportar backup
|
||||
./adn/tools/run dron lanzar --nota "Exportar backup" --nodo srvv-fenix -- \
|
||||
ruby adn/tools/cli/drones/dasuten_exportar.rb
|
||||
|
||||
# Paso 2: Upload a Drive
|
||||
./adn/tools/run dron lanzar --nota "Upload Drive" --nodo srv-ns8 -- \
|
||||
ruby adn/tools/cli/drones/dasuten_upload_drive.rb
|
||||
|
||||
# Paso 3: Download desde Drive
|
||||
./adn/tools/run dron lanzar --nota "Download Drive" --nodo dasu-sql4 -- \
|
||||
ruby adn/tools/cli/drones/dasuten_download_drive.rb
|
||||
|
||||
# Paso 4: Restaurar backup
|
||||
./adn/tools/run dron lanzar --nota "Restaurar backup" --nodo dasu-sql4 -- \
|
||||
ruby adn/tools/cli/drones/dasuten_restaurar.rb
|
||||
```
|
||||
|
||||
### Ejecutar desde un Paso Específico
|
||||
|
||||
```bash
|
||||
# Comenzar desde upload (saltear export)
|
||||
./adn/tools/run dron lanzar --nota "Pipeline DASUTEN" -- \
|
||||
ruby adn/tools/cli/drones/orquestador_pipeline.rb --paso upload
|
||||
```
|
||||
|
||||
### Dry-Run (Simulación)
|
||||
|
||||
```bash
|
||||
# Ver qué haría sin ejecutar
|
||||
./adn/tools/run dron lanzar --nota "Pipeline DASUTEN" -- \
|
||||
ruby adn/tools/cli/drones/orquestador_pipeline.rb --dry-run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Ventajas de esta Arquitectura
|
||||
|
||||
| Ventaja | Descripción |
|
||||
|---------|-------------|
|
||||
| **🧩 Atomicidad** | Cada dron hace UNA cosa. Fácil de testear, debuggear, reemplazar. |
|
||||
| **🔗 Intercomunicación** | JSON como contrato entre drones. Lenguaje-agnóstico. |
|
||||
| **🔄 Re-ejecución** | Si falla el paso 3, re-ejecutar solo paso 3 (no todo el pipeline). |
|
||||
| **👁️ Observabilidad** | Cada dron se registra individualmente en Bitácora Web. |
|
||||
| **🛡️ Tolerancia a Fallos** | Si un dron falla, el pipeline se detiene (fail-fast). |
|
||||
| **📈 Escalabilidad** | Agregar nuevos pasos = agregar nuevo dron + registrar en ESTADOS. |
|
||||
|
||||
---
|
||||
|
||||
## 📁 Estructura de Archivos
|
||||
|
||||
```
|
||||
adn/tools/cli/drones/
|
||||
├── orquestador_pipeline.rb # Orquestador principal
|
||||
├── dasuten_exportar.rb # Dron 1: Exportar backup
|
||||
├── dasuten_upload_drive.rb # Dron 2: Upload a Drive
|
||||
├── dasuten_download_drive.rb # Dron 3: Download desde Drive
|
||||
└── dasuten_restaurar.rb # Dron 4: Restaurar backup
|
||||
|
||||
/tmp/
|
||||
├── dron_export_output.json # Output del dron 1
|
||||
├── dron_upload_output.json # Output del dron 2
|
||||
├── dron_download_output.json # Output del dron 3
|
||||
└── dron_restore_output.json # Output del dron 4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Futuras Mejoras (Fases Pendientes)
|
||||
|
||||
### Fase 2: Persistencia en Bitácora
|
||||
|
||||
Actualmente los outputs JSON son efímeros (`/tmp/`). La **Fase 2** persistirá los outputs en la Bitácora Web:
|
||||
|
||||
```ruby
|
||||
# En dron_db.rb
|
||||
CREATE TABLE drone_outputs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
dron_id VARCHAR(50) REFERENCES drones(dron_id),
|
||||
output JSONB NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- El orquestador puede leer outputs de la DB en lugar de archivos
|
||||
output = DronDB.obtener_output(dron_id)
|
||||
```
|
||||
|
||||
### Fase 3: Reintentos Automáticos
|
||||
|
||||
```ruby
|
||||
# Orquestador con retry
|
||||
def lanzar_dron_con_retry(script, nodo, nota, timeout, max_retries: 3)
|
||||
max_retries.times do |intento|
|
||||
exito, resultado = lanzar_dron(script, nodo, nota, timeout)
|
||||
return true, resultado if exito
|
||||
|
||||
puts "⚠ Dron falló (intento #{intento + 1}/#{max_retries})"
|
||||
sleep(2 ** intento) # Backoff exponencial
|
||||
end
|
||||
return false, {}
|
||||
end
|
||||
```
|
||||
|
||||
### Fase 4: Condicionales de Skip
|
||||
|
||||
```ruby
|
||||
# Skip si ya está hecho
|
||||
def deberia_ejecutar?(paso, contexto)
|
||||
case paso
|
||||
when :upload
|
||||
# Skip si ya hay backup reciente en Drive
|
||||
!backup_reciente_en_drive?(contexto[:backup_ruta])
|
||||
when :restaurar
|
||||
# Skip si BD ya está actualizada
|
||||
!bd_actualizada?(contexto[:backup_local])
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Lecciones Aprendidas
|
||||
|
||||
### ✅ Lo que Funcionó
|
||||
|
||||
| Lección | Descripción |
|
||||
|---------|-------------|
|
||||
| **JSON como contrato** | Simple, legible, lenguaje-agnóstico. Funciona mejor que DB compartida. |
|
||||
| **Outputs en /tmp** | Fácil de limpiar, no requiere permisos especiales. |
|
||||
| **Orquestador stateless** | El estado está en los JSONs, no en el orquestador. |
|
||||
| **Drones auto-bitácora** | Cada dron se registra individualmente en la Bitácora Web. |
|
||||
| **Google Drive como intermediario** | Elimina dependencia de Tailscale inestable. Download HTTP directo desde dasu-sql4. |
|
||||
| **Base64 encoding para PowerShell** | Evita problemas de escaping UTF-8 al transportar scripts sobre SSH. |
|
||||
| **sshpass + ProxyCommand** | Patrón efectivo para acceder a VMs detrás de relay (dasu-sql4 detrás de srv-dasu). |
|
||||
|
||||
### 📊 Resultados del Pipeline DASUTEN (2026-04-14)
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| **Archivo transferido** | 1.3 GB (`sysdasuten_compressed_ADN.bak`) |
|
||||
| **Tiempo total de pipeline** | ~35 minutos |
|
||||
| **Upload a Drive** | 29 minutos (1736s) — limitado por ancho de banda |
|
||||
| **Download HTTP** | ~5 minutos — con confirmación de virus manejada |
|
||||
| **Restore SQL** | 0.23 segundos — MOVE instantáneo |
|
||||
| **DBCC CHECKDB** | 248.56 segundos — **Integridad: OK** |
|
||||
| **Throughput efectivo** | ~650 KB/s (limitado por subida a Drive) |
|
||||
|
||||
### ⚠️ Lo que Requiere Atención
|
||||
|
||||
| Lección | Descripción |
|
||||
|---------|-------------|
|
||||
| **Limpieza de archivos** | Los JSONs en `/tmp/` deben limpiarse post-pipeline. |
|
||||
| **Timeouts por dron** | Cada dron debe tener timeout individual (no todos duran lo mismo). |
|
||||
| **Manejo de errores** | El orquestador debe capturar stderr de cada dron para debugging. |
|
||||
|
||||
### 🔧 Desafíos Superados
|
||||
|
||||
| Desafío | Solución |
|
||||
|---------|----------|
|
||||
| **Virus confirmation page** | Parseo de HTML con regex PowerShell para extraer `id`, `uuid`, `confirm` |
|
||||
| **dasu-sql4 sleep mode** | Acceso vía relay SSH desde srv-dasu con `sshpass -e` + ProxyCommand |
|
||||
| **PowerShell escaping** | Base64 encoding del script completo + heredoc con `'PSCMD'` (sin interpolación) |
|
||||
| **sqlcmd no disponible en PATH** | Usar ruta completa: `C:\Program Files\...\sqlcmd.exe` |
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Referencias
|
||||
|
||||
- **Ámbito**: [A01_dtic-ADN.md](./A01_dtic-ADN.md)
|
||||
- **Evolución Dron**: [A01.P009_Evolucion-Dron-ADN.md](./A01.P009_Evolucion-Dron-ADN.md)
|
||||
- **Drones Ops**: [A01.P011_Drones-Operaciones-Infra.md](./A01.P011_Drones-Operaciones-Infra.md)
|
||||
- **Caso de Uso**: [A04.P005_DASUTEN-sin-DC.md](../dtic-DASUTEN/A04.P005_DASUTEN-sin-DC.md) — Pipeline implementado para Fase 8b
|
||||
|
||||
---
|
||||
|
||||
## 📊 Estado del Pipeline DASUTEN (6 drones atómicos)
|
||||
|
||||
| Paso | Estado | Dron | Nodo | Output | Duración |
|
||||
|------|--------|------|------|--------|----------|
|
||||
| **8b.1** | ✅ Completado | `dasuten_exportar_srvv-fenix.rb` | srvv-fenix | `backup_ruta` | 44.8s |
|
||||
| **8b.2** | ✅ Completado | `dasuten_transferir_srvv-fenix-srv-ns8.rb` | srv-ns8 | `backup_local` | ~2 min |
|
||||
| **8b.3** | ✅ Completado | `dasuten_upload_srv-ns8-drive.rb` | srv-ns8 | `drive_ruta` | 1736s (29 min) |
|
||||
| **8b.4** | ✅ Completado | `dasuten_download_drive-dasu-sql4.rb` | dasu-sql4 | `backup_local` | ~5 min |
|
||||
| **8b.5** | ✅ Completado | `dasuten_restaurar_dasu-sql4.rb` | dasu-sql4 | `estado_integridad=PENDING` | 0.23s |
|
||||
| **8b.6** | ✅ Completado | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | `estado_integridad=OK` | 248.56s |
|
||||
|
||||
**Pipeline completado exitosamente el 2026-04-14 a las 17:21.**
|
||||
|
||||
### Resultados Finales
|
||||
- **Backup comprimido:** 1.3 GB (`sysdasuten_compressed_ADN.bak`)
|
||||
- **Google Drive URL:** `rmonla-GDrive:drive_bkps-dasu/sysdasuten_compressed_ADN_20260414_*.bak`
|
||||
- **Download HTTP directo:** Con confirmación de virus manejada (formulario HTML parsing)
|
||||
- **Restore:** 0.23 segundos (MOVE a F:\DATA y F:\LOG)
|
||||
- **DBCC CHECKDB:** 248.56 segundos — **Integridad: OK** (1,186,793 páginas verificadas, sin errores)
|
||||
- **Duración total del pipeline:** ~35 minutos
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Pipeline Diferencial (Refresco Diario)
|
||||
|
||||
Para refrescos diarios de la base de datos, se recomienda usar **backup diferencial** que solo transfiere los cambios desde el último backup completo.
|
||||
|
||||
### Ventajas del Pipeline Diferencial
|
||||
|
||||
| Métrica | Completo | Diferencial |
|
||||
|---------|----------|-------------|
|
||||
| **Tamaño backup** | 1.3 GB | ~50-100 MB* |
|
||||
| **Tiempo export** | 45 seg | ~5-10 seg |
|
||||
| **Tiempo upload** | 29 min | ~2-3 min |
|
||||
| **Tiempo download** | ~5 min | ~30 seg |
|
||||
| **Tiempo total** | ~35 min | ~5-10 min |
|
||||
|
||||
\* Depende de la cantidad de cambios diarios
|
||||
|
||||
### Drones del Pipeline Diferencial
|
||||
|
||||
| Paso | Dron | Nodo | Tarea |
|
||||
|------|------|------|-------|
|
||||
| **1** | `dasuten_exportar_diferencial_srvv-fenix.rb` | srvv-fenix | `BACKUP DATABASE ... WITH DIFFERENTIAL` |
|
||||
| **2** | `dasuten_transferir_srvv-fenix-srv-ns8.rb` | srv-ns8 | SMB → `/var/tmp/` |
|
||||
| **3** | `dasuten_upload_srv-ns8-drive.rb` | srv-ns8 | rclone → Google Drive |
|
||||
| **4** | `dasuten_download_drive-dasu-sql4.rb` | dasu-sql4 | HTTP ← Google Drive |
|
||||
| **5** | `dasuten_restaurar_diferencial_dasu-sql4.rb` | dasu-sql4 | `RESTORE ... WITH DIFFERENTIAL` + `RECOVERY` |
|
||||
| **6** | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | `DBCC CHECKDB` |
|
||||
|
||||
### Orquestador Diferencial
|
||||
|
||||
```bash
|
||||
# Ejecutar pipeline diferencial completo
|
||||
./adn/tools/run dron lanzar --nota "Refresco Diario DASUTEN" -- \
|
||||
ruby adn/tools/cli/drones/orquestador_pipeline_diferencial.rb
|
||||
```
|
||||
|
||||
### Estrategia Recomendada
|
||||
|
||||
| Día | Tipo de Backup | Duración Estimada |
|
||||
|-----|----------------|-------------------|
|
||||
| **Lunes** | Completo | ~35 min |
|
||||
| **Martes** | Diferencial | ~5-10 min |
|
||||
| **Miércoles** | Diferencial | ~5-10 min |
|
||||
| **Jueves** | Diferencial | ~5-10 min |
|
||||
| **Viernes** | Diferencial | ~5-10 min |
|
||||
| **Sábado** | Diferencial | ~5-10 min |
|
||||
| **Domingo** | — (sin cambios) | — |
|
||||
|
||||
### Consideraciones Importantes
|
||||
|
||||
1. **El backup diferencial se basa en el último backup completo**, no en el diferencial anterior
|
||||
2. **Cada diferencial es acumulativo** — el martes tiene cambios desde lunes, el miércoles tiene cambios desde lunes (no desde martes)
|
||||
3. **Restaurar diferencial requiere:**
|
||||
- Último backup completo (ya restaurado en dasu-sql4)
|
||||
- Backup diferencial más reciente
|
||||
- `WITH NORECOVERY` para aplicar diferencial, luego `WITH RECOVERY` para poner BD online
|
||||
|
||||
---
|
||||
|
||||
*Documento creado: 2026-04-14*
|
||||
*Última actualización: 2026-04-14*
|
||||
*Versión: 2.1 — Con pipeline diferencial*
|
||||
Reference in New Issue
Block a user