Files
oc-scheduler/docs/nats.md

141 lines
5.8 KiB
Go
Raw Normal View History

2026-02-23 18:10:47 +01:00
# 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 + -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
```