# NATS dans oc-scheduler ## Vue d'ensemble `oc-scheduler` utilise NATS comme bus d'événements pour deux objectifs : 1. **Recevoir les planners** (disponibilité des ressources) publiés par `oc-discovery`. 2. **Réagir aux modifications de workflows** pour diffuser un planner actualisé et signaler les streams WebSocket actifs. Tout le code NATS se trouve dans `infrastructure/nats.go`. --- ## Canaux écoutés ### `PROPALGATION_EVENT` — réception des planners **Condition d'acceptation :** `resp.FromApp == "oc-discovery"` et `prop.Action == PB_PLANNER`. **Ce qui se passe :** - Le payload est désérialisé en `planner.Planner`. - Le champ `peer_id` est extrait pour identifier le pair. - Le planner est stocké dans `PlannerCache[peerID]` via `storePlanner()`. - Si c'est la **première apparition** de ce `peerID` dans le cache, une goroutine de TTL est lancée (voir §TTL ci-dessous). - Tous les abonnés en attente d'un changement sur ce `peerID` sont notifiés. ### `CREATE_RESOURCE` — modification d'un workflow **Condition d'acceptation :** `resp.Datatype == WORKFLOW`. **Ce qui se passe :** 1. Le payload est désérialisé en `workflow.Workflow`. 2. `broadcastPlanner(wf)` est appelé : pour chaque pair (storage + compute) du workflow dont le planner **n'est pas encore en cache**, un événement `PB_PLANNER` est émis sur NATS afin de demander un planner frais à `oc-discovery`. 3. `notifyWorkflowWatchers(wf.GetID())` est appelé : tous les streams WebSocket qui observent ce workflow sont signalés pour **rafraîchir leur liste de pairs surveillés**. --- ## Canaux émis ### `PROPALGATION_EVENT` — deux actions possibles | Action | Déclencheur | Effet attendu | |---|---|---| | `PB_PLANNER` | Workflow modifié, pair inconnu du cache | `oc-discovery` renvoie le planner du pair | | `PB_CLOSE_PLANNER` | TTL expiré **ou** déconnexion WebSocket | Les consommateurs (oc-discovery, autres schedulers) libèrent leur état pour ce pair | --- ## Cache des planners (`PlannerCache`) ``` PlannerCache : map[string]*planner.Planner // clé = peerID plannerAddedAt : map[string]time.Time // horodatage de première insertion ``` - Protégé par `plannerMu` (RWMutex). - Alimenté uniquement via `storePlanner()` (appelé par le listener NATS). - Supprimé via `EmitNATS(peerID, PB_CLOSE_PLANNER)`, qui efface l'entrée **et** notifie les abonnés. ### TTL de 24 heures À la **première** insertion d'un `peerID`, une goroutine est lancée : ``` sleep(24h) → si l'entrée existe encore : EmitNATS(peerID, PB_CLOSE_PLANNER) ``` Cela évite que des planners obsolètes stagnent indéfiniment. L'entrée est supprimée et les streams actifs reçoivent une notification « plus de planner » pour ce pair. --- ## Pub/sub interne Un registre d'abonnements en mémoire permet à d'autres composants (notamment le controller WebSocket) de réagir aux événements sans coupler directement le code NATS et les goroutines HTTP. Deux registres distincts : | Registre | Clé | Signification | |---|---|---| | `plannerSubs` | `peerID` | « le planner de ce pair a changé » | | `workflowSubs` | `workflowID` | « ce workflow a été modifié » | ### API ```go // S'abonner aux changements de planners pour plusieurs pairs ch, cancel := SubscribePlannerUpdates(peerIDs []string) // S'abonner aux modifications d'un workflow ch, cancel := SubscribeWorkflowUpdates(wfID string) ``` Chaque canal est bufférisé (`capacity 1`) : si un signal est déjà en attente, les suivants sont ignorés sans bloquer. --- ## Intégration avec le stream WebSocket (`GET /oc/:id/check`) Le handler `CheckStream` dans `controllers/workflow_sheduler.go` exploite ces mécanismes : 1. **Ouverture** : résolution des `peerIDs` du workflow, abonnement à `SubscribePlannerUpdates` et `SubscribeWorkflowUpdates`. 2. **Boucle de streaming** : - `plannerCh` reçoit un signal → re-calcul du `CheckResult` et envoi au client. - `wfCh` reçoit un signal (workflow modifié) → recalcul des `peerIDs`, désabonnement + ré-abonnement aux nouveaux pairs, re-calcul et envoi. 3. **Fermeture** (déconnexion client) : - Désabonnement des deux registres. - `EmitNATS(peerID, PB_CLOSE_PLANNER)` pour **chaque pair surveillé** : le cache est purgé et `oc-discovery` est informé que le scheduler n'a plus besoin du planner. --- ## Flux de données résumé ``` oc-discovery ──PROPALGATION_EVENT(PB_PLANNER)──► ListenNATS │ storePlanner() PlannerCache[peerID] = planner notifyPlannerWatchers(peerID) │ SubscribePlannerUpdates │ CheckStream (WS) ──► client Workflow modifié ──CREATE_RESOURCE(WORKFLOW)──► ListenNATS │ broadcastPlanner(wf) PROPALGATION_EVENT(PB_PLANNER) → oc-discovery notifyWorkflowWatchers(wfID) │ SubscribeWorkflowUpdates │ CheckStream refresh peerIDs ──► client TTL 24h / déconnexion WS ──► EmitNATS(PB_CLOSE_PLANNER) │ delete PlannerCache[peerID] notifyPlannerWatchers(peerID) PROPALGATION_EVENT(PB_CLOSE_PLANNER) → NATS bus ```