# 03 – Spécification API Backend (FastAPI) Objectif : définir précisément les endpoints backend, les schémas JSON échangés et les règles métier associées. L’API sera exposée sous `/api`. --- ## 1. Contenu attendu de ce fichier Ce document doit servir de référence au développeur backend. Il décrit : 1. Le format du payload JSON envoyé par le script de benchmark (`bench.sh`) vers le backend. 2. Les endpoints REST nécessaires : - Réception d’un benchmark. - Consultation des devices. - Consultation de l’historique des benchmarks. - Gestion des liens constructeur. - Gestion des documents (PDF, images). 3. Les règles d’authentification (token simple). 4. Les codes d’erreur principaux et attentes de validation. --- ## 2. Schéma JSON – payload d’un benchmark Endpoint cible : `POST /api/benchmark` Le script client envoie un JSON de la forme : ```json { "device_identifier": "elitedesk-800g3", "bench_script_version": "1.0.0", "hardware": { "cpu": { "vendor": "Intel", "model": "Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz", "microarchitecture": "Skylake", "cores": 4, "threads": 8, "base_freq_ghz": 3.4, "max_freq_ghz": 4.0, "cache_l1_kb": 256, "cache_l2_kb": 1024, "cache_l3_kb": 8192, "flags": ["avx", "avx2", "aes"], "tdp_w": 65 }, "ram": { "total_mb": 65536, "slots_total": 4, "slots_used": 4, "ecc": false, "layout": [ { "slot": "DIMM_A1", "size_mb": 16384, "type": "DDR4", "speed_mhz": 2133, "vendor": "Corsair", "part_number": "XYZ" } ] }, "gpu": { "vendor": "Intel", "model": "Intel HD Graphics 530", "driver_version": "xxx", "memory_dedicated_mb": null, "memory_shared_mb": 512, "api_support": ["OpenGL 4.5"] }, "storage": { "devices": [ { "name": "/dev/nvme0n1", "type": "NVMe", "interface": "PCIe 3.0 x4", "capacity_gb": 1000, "vendor": "Samsung", "model": "970 EVO Plus", "smart_health": "PASSED", "temperature_c": 42 } ], "partitions": [ { "name": "/dev/nvme0n1p1", "mount_point": "/", "fs_type": "ext4", "used_gb": 50, "total_gb": 100 } ] }, "network": { "interfaces": [ { "name": "eth0", "type": "ethernet", "mac": "aa:bb:cc:dd:ee:ff", "ip": "10.0.0.10", "speed_mbps": 1000, "driver": "e1000e" } ] }, "motherboard": { "vendor": "HP", "model": "HP 8054", "bios_version": "P01 Ver. 02.48", "bios_date": "2023-05-10" }, "os": { "name": "Debian", "version": "12 (bookworm)", "kernel_version": "6.1.0-xx-amd64", "architecture": "x86_64", "virtualization_type": "none" }, "sensors": { "cpu_temp_c": 45, "disk_temps_c": { "/dev/nvme0n1": 42 } }, "raw_info": { "lscpu": "raw text…", "lsblk": "raw text…" } }, "results": { "cpu": { "events_per_sec": 12000, "duration_s": 10, "score": 90 }, "memory": { "throughput_mib_s": 20000, "score": 95 }, "disk": { "read_mb_s": 1200, "write_mb_s": 1000, "iops_read": 50000, "iops_write": 45000, "latency_ms": 1.2, "score": 94 }, "network": { "upload_mbps": 930, "download_mbps": 940, "ping_ms": 1.2, "jitter_ms": 0.3, "packet_loss_percent": 0.0, "score": 88 }, "gpu": { "glmark2_score": null, "score": null }, "global_score": 92 } } ``` Remarques importantes : - Certains champs peuvent être `null` ou absents en fonction de la machine (ex: pas de GPU dédié). - Le backend doit être robuste à l’absence de certains blocs (ex: pas de `network` si aucun test réseau). --- ## 3. Logique de traitement côté backend 1. **Auth** - Lecture du header `Authorization: Bearer `. - Comparaison avec une valeur stockée dans la config/ENV (`API_TOKEN`). - Si token invalide ou absent : réponse 401. 2. **Résolution du device** - Chercher un enregistrement `devices` avec `hostname == device_identifier`. - Si trouvé : - Utiliser `device_id` existant. - Sinon : - Créer un nouveau `device` minimal avec : - `hostname = device_identifier` - `created_at`, `updated_at` = maintenant. 3. **Création d’un hardware_snapshot** - Mapper le contenu `hardware` vers la table `hardware_snapshots` telle que définie dans `02_model_donnees.md`. - Exemples : - `hardware.cpu.vendor` -> `cpu_vendor` - `hardware.ram.total_mb` -> `ram_total_mb` - `hardware.storage.devices` -> JSON dans `storage_devices_json` - etc. - `captured_at` = `now()` (ou `run_at` du benchmark si fourni). 4. **Création d’un benchmark** - `run_at` = `now()` (ou fourni explicitement plus tard). - `bench_script_version` = champ du payload. - Scores : - `cpu_score` = `results.cpu.score` (si présent). - `memory_score` = `results.memory.score` - `disk_score` = `results.disk.score` - `network_score` = `results.network.score` - `gpu_score` = `results.gpu.score` - `global_score` = `results.global_score` - `details_json` = JSON complet de `results` (ou éventuellement de `results` + métriques brutes). 5. **Réponse** - En cas de succès, renvoyer : ```json { "status": "ok", "device_id": 1, "benchmark_id": 42 } ``` --- ## 4. Endpoints détaillés ### 4.1. POST /api/benchmark - Rôle : point d’entrée unique des résultats du script client. - Auth : header `Authorization: Bearer `. - Body : JSON (voir schéma ci-dessus). - Codes de réponse : - `200` – succès. - `400` – JSON invalide / validation Pydantic échouée. - `401` – token invalide ou manquant. - `500` – erreur interne. --- ### 4.2. GET /api/devices Objectif : lister les devices avec un résumé de leur dernier benchmark. Paramètres query : - `page` (int, optionnel, défaut 1) - `page_size` (int, optionnel, défaut 20) - `search` (string, optionnel – filtre sur hostname/description/tags) Réponse (exemple simplifié) : ```json { "items": [ { "id": 1, "hostname": "elitedesk-800g3", "description": "HP EliteDesk 800 G3 SFF", "location": "Bureau", "tags": ["lab", "dev"], "last_benchmark": { "id": 42, "run_at": "2025-12-07T10:32:00Z", "global_score": 92, "cpu_score": 90, "memory_score": 95, "disk_score": 94, "network_score": 88, "gpu_score": null } } ], "total": 1, "page": 1, "page_size": 20 } ``` --- ### 4.3. GET /api/devices/{device_id} Objectif : récupérer le détail d’un device. Réponse (exemple) : ```json { "id": 1, "hostname": "elitedesk-800g3", "fqdn": "elitedesk-800g3.maison43", "description": "HP EliteDesk 800 G3 SFF (serveur dev)", "asset_tag": "LAB-001", "location": "Bureau", "owner": "Gilles", "tags": ["lab", "proxmox"], "created_at": "2025-12-01T10:00:00Z", "updated_at": "2025-12-07T10:32:00Z", "last_hardware_snapshot": { "captured_at": "2025-12-07T10:32:00Z", "cpu_vendor": "Intel", "cpu_model": "Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz", "cpu_cores": 4, "cpu_threads": 8, "ram_total_mb": 65536, "storage_summary": "NVMe 1TB + HDD 2TB", "gpu_summary": "Intel HD Graphics 530", "os_name": "Debian", "os_version": "12 (bookworm)", "kernel_version": "6.1.0-xx-amd64" }, "last_benchmark": { "id": 42, "run_at": "2025-12-07T10:32:00Z", "global_score": 92, "cpu_score": 90, "memory_score": 95, "disk_score": 94, "network_score": 88, "gpu_score": null } } ``` --- ### 4.4. GET /api/devices/{device_id}/benchmarks Objectif : récupérer l’historique de benchmarks d’un device. Paramètres : - `limit` (int, défaut 20) - `offset` (int, défaut 0) Réponse : ```json { "items": [ { "id": 42, "run_at": "2025-12-07T10:32:00Z", "global_score": 92, "cpu_score": 90, "memory_score": 95, "disk_score": 94, "network_score": 88, "gpu_score": null }, { "id": 35, "run_at": "2025-12-05T09:10:00Z", "global_score": 89, "cpu_score": 88, "memory_score": 92, "disk_score": 90, "network_score": 85, "gpu_score": null } ], "total": 2, "limit": 20, "offset": 0 } ``` --- ### 4.5. GET /api/benchmarks/{benchmark_id} Objectif : récupérer les détails complets d’un benchmark. Réponse : ```json { "id": 42, "device_id": 1, "hardware_snapshot_id": 10, "run_at": "2025-12-07T10:32:00Z", "bench_script_version": "1.0.0", "global_score": 92, "cpu_score": 90, "memory_score": 95, "disk_score": 94, "network_score": 88, "gpu_score": null, "details": { "cpu": { "events_per_sec": 12000, "duration_s": 10, "score": 90 }, "memory": { "throughput_mib_s": 20000, "score": 95 }, "disk": { "read_mb_s": 1200, "write_mb_s": 1000, "iops_read": 50000, "iops_write": 45000, "latency_ms": 1.2, "score": 94 }, "network": { "upload_mbps": 930, "download_mbps": 940, "ping_ms": 1.2, "jitter_ms": 0.3, "packet_loss_percent": 0, "score": 88 }, "gpu": { "glmark2_score": null, "score": null }, "global_score": 92 } } ``` Note : `details` reflète `details_json` de la base. --- ### 4.6. Liens constructeur #### GET /api/devices/{device_id}/links Réponse : ```json [ { "id": 1, "label": "Support HP", "url": "https://support.hp.com/..." }, { "id": 2, "label": "Page produit", "url": "https://www.hp.com/..." } ] ``` #### POST /api/devices/{device_id}/links Body : ```json { "label": "Support HP", "url": "https://support.hp.com/..." } ``` Réponse : l’objet créé avec `id`. #### PUT /api/links/{id} Body idem POST (label + url). #### DELETE /api/links/{id} - Réponse : `204 No Content` en cas de succès. --- ### 4.7. Documents #### GET /api/devices/{device_id}/docs Réponse : ```json [ { "id": 1, "doc_type": "manual", "filename": "manual_elitedesk.pdf", "mime_type": "application/pdf", "size_bytes": 3456789, "uploaded_at": "2025-05-01T10:00:00Z" } ] ``` #### POST /api/devices/{device_id}/docs - Type : `multipart/form-data` - Champs : - `file` (fichier binaire) - `doc_type` (string) Réponse : métadonnées du document créé. #### GET /api/docs/{doc_id}/download - Sert le fichier binaire avec le bon `Content-Type`. --- ## 5. Authentification MVP : token simple. - Variable d’env `API_TOKEN` côté backend. - Chaque requête `POST /api/benchmark` doit inclure : ```http Authorization: Bearer ``` - Les endpoints de lecture (GET) peuvent être : - soit publics sur le LAN, - soit protégés par un autre mécanisme (reverse proxy) – hors scope de cette spec. --- ## 6. Gestion des erreurs et validations - Utiliser des modèles Pydantic pour : - valider la structure de `hardware` et `results` autant que possible sans être bloquant. - autoriser des champs optionnels (None / manquants). - Codes d’erreur génériques : - 400 : données invalides (schema). - 401 : auth. - 404 : ressources inexistantes (device, benchmark, link, doc). - 413 : payload trop volumineux (facultatif selon config). - 500 : erreur interne. Logs : - Logguer les erreurs de parsing JSON. - Logguer l’origine (IP) des appels `POST /api/benchmark`. ---