Decentralized
This commit is contained in:
140
docs/nats.md
Normal file
140
docs/nats.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user