From 9d4d1b38b322f3c6a2f9b685db9f79e4edd8bafd Mon Sep 17 00:00:00 2001 From: GnomeZworc Date: Mon, 18 May 2026 19:47:02 +0200 Subject: [PATCH] fix doc with project reality Signed-off-by: GnomeZworc --- Agent Network.md | 366 +++++++++++++++---------------------------- Agent Persistance.md | 148 +++++++---------- Event Bus.md | 220 +++++++++++--------------- _Sidebar.md | 2 +- vnc.md | 183 ++++++++++++++++++++++ 5 files changed, 465 insertions(+), 454 deletions(-) create mode 100644 vnc.md diff --git a/Agent Network.md b/Agent Network.md index ab0733d..b7d5083 100644 --- a/Agent Network.md +++ b/Agent Network.md @@ -1,269 +1,157 @@ -# Le reseaux entre les vms +# Réseau entre les VMs + +## Topologie ``` -+--------------------------------------------------------------+ +----------------------------------------------------+ -| HOST A | | HOST B | -| | | | -| [ netns-1 (VPC 1) ] | | | -| | | | -| br-subnet-1 (10.0.1.1/32) ---- veth1 <----------------+ | | | -| (dans netns) | | | -| | | | -| br-subnet-2 (10.0.2.1/32) ---- veth2 <----------------+ | | | -| (dans netns) | | | -| | | | -| | | | -| [ netns-2 (VPC 2) ] | | [ netns-2 (VPC 2) ] | -| | | | -| br-subnet-3 (10.0.3.1/32) ---- veth3 <------+ | | br-subnet-3 (10.0.3.1/32) ---- veth4 <------+ | -| (dans netns) | | | (dans netns) | | -| | | | | | -+-------------------------------------------------+------------+ +-------------------+--------------------------------+ - | | - | | - +----------v-----------+ +-----------v----------+ - | br-vx-1 (host) | | br-vx-1 (host) | - | (infra bridge) | | (infra bridge) | - +----------------------+ +----------------------+ - | | - +-----------v-----------+ +----------v------------+ - | vxlan1001 | | vxlan1001 | - | (Tunnel inter-host) | | (Tunnel inter-host) | - +-----------------------+ +-----------------------+ - +Root netns +├── vp--e ← veth externe vers le netns VPC +├── br- ← bridge subnet (côté root) +│ ├── v--e ← veth externe subnet +│ └── vxlan- ← tunnel inter-host +│ +netns. +├── br-public ← bridge d'accès public du VPC +│ └── vp--i ← veth interne VPC +├── br- ← bridge subnet (côté VPC, isolé) +│ ├── v--i ← veth interne subnet +│ └── tap ← interface de la VM +└── ... ``` +Chaque VPC est un **Linux network namespace**. Les subnets sont des bridges isolés dans ce namespace. Le trafic inter-host passe par des tunnels **VXLAN** sur les bridges root. Les VMs attachent via des interfaces **TAP**. + +## Packages + +| Package | Rôle | +|---|---| +| `internal/netns` | Cycle de vie des network namespaces | +| `internal/netif` | Création/suppression d'interfaces (bridge, veth, vxlan, tap) | +| `internal/subnet` | Orchestration complète de création/suppression d'un subnet | +| `internal/vpc` | Orchestration complète de création/suppression d'un VPC | +| `internal/dhcp` | Génération de config dnsmasq + stockage des entrées DHCP | +| `internal/ebtables` | Règles L2 (DROP ARP/DHCP) sur les bridges | +| `internal/iptables` | Redirection NAT pour le serveur metadata | +| `pkg/systemd` | Start/Stop de services systemd via D-Bus | + +## `internal/netns` + ```go -package netutils - -import ( - "fmt" - "net" - - "github.com/vishvananda/netlink" - "github.com/vishvananda/netns" - "golang.org/x/sys/unix" -) - -// CreateNetns crée un nouveau namespace nommé -func CreateNetns(name string) error { - path := fmt.Sprintf("/var/run/netns/%s", name) - return unix.Mount("/proc/self/ns/net", path, "none", unix.MS_BIND, "") -} - -// CreateBridge crée un bridge dans le namespace donné (ou root si nsPath == "") -func CreateBridge(name, nsPath string) error { - link := &netlink.Bridge{ - LinkAttrs: netlink.LinkAttrs{Name: name}, - } - if nsPath != "" { - newNs, err := netns.GetFromPath(nsPath) - if err != nil { - return err - } - defer newNs.Close() - - return netns.Do(newNs, func(_ netns.NsHandle) error { - return netlink.LinkAdd(link) - }) - } - return netlink.LinkAdd(link) -} - -// LinkBridgeNetns connecte deux bridges via un veth pair (br1 dans root, br2 dans netns) -func LinkBridgeNetns(vethName, peerName, netnsPath, bridgeRoot, bridgeInNs string) error { - peer := netlink.Veth{ - LinkAttrs: netlink.LinkAttrs{Name: vethName}, - PeerName: peerName, - } - - if err := netlink.LinkAdd(&peer); err != nil { - return err - } - - // Set peer into target netns - ns, err := netns.GetFromPath(netnsPath) - if err != nil { - return err - } - defer ns.Close() - - linkPeer, err := netlink.LinkByName(peerName) - if err != nil { - return err - } - if err := netlink.LinkSetNsFd(linkPeer, int(ns)); err != nil { - return err - } - - // Attach veth to host bridge - br, err := netlink.LinkByName(bridgeRoot) - if err != nil { - return err - } - veth, _ := netlink.LinkByName(vethName) - if err := netlink.LinkSetMaster(veth, br); err != nil { - return err - } - if err := netlink.LinkSetUp(veth); err != nil { - return err - } - - // Attach peer to bridge in netns - return netns.Do(ns, func(_ netns.NsHandle) error { - peerLink, err := netlink.LinkByName(peerName) - if err != nil { - return err - } - brInNs, err := netlink.LinkByName(bridgeInNs) - if err != nil { - return err - } - if err := netlink.LinkSetMaster(peerLink, brInNs); err != nil { - return err - } - return netlink.LinkSetUp(peerLink) - }) -} - -// AddIPToBridge ajoute une IP en /32 à un bridge dans un namespace -func AddIPToBridge(nsPath, bridgeName, ipCidr string) error { - ns, err := netns.GetFromPath(nsPath) - if err != nil { - return err - } - defer ns.Close() - - return netns.Do(ns, func(_ netns.NsHandle) error { - br, err := netlink.LinkByName(bridgeName) - if err != nil { - return err - } - addr, err := netlink.ParseAddr(ipCidr) - if err != nil { - return err - } - return netlink.AddrAdd(br, addr) - }) -} - -// AddLinkLocalRoute ajoute une route link-local dans un netns -func AddLinkLocalRoute(nsPath string) error { - ns, err := netns.GetFromPath(nsPath) - if err != nil { - return err - } - defer ns.Close() - - return netns.Do(ns, func(_ netns.NsHandle) error { - _, dst, _ := net.ParseCIDR("fe80::/64") - route := &netlink.Route{ - Dst: dst, - Scope: netlink.SCOPE_LINK, - } - return netlink.RouteAdd(route) - }) -} - -// CleanupNetns démonte et supprime un netns (utilise iproute2 fs) -func CleanupNetns(name string) error { - path := fmt.Sprintf("/var/run/netns/%s", name) - if err := unix.Unmount(path, 0); err != nil { - return err - } - return unix.Unlink(path) -} - -// DeleteLink supprime un lien réseau (bridge, veth, etc.) -func DeleteLink(name string) error { - link, err := netlink.LinkByName(name) - if err != nil { - return err - } - return netlink.LinkDel(link) -} +func Create(name string) error // CLONE_NEWNET + bind mount sur /var/run/netns/ +func Delete(name string) error // unmount + suppression du fichier +func Call(name string, fn func() error) error // exécute fn dans le netns, restaure le ns d'origine +func Enter(name string) error // entre définitivement dans le netns +func Exist(name string) bool ``` +La création utilise `unix.Unshare(CLONE_NEWNET)` + bind mount du thread courant (`/proc/self/task//ns/net`) — pas un bind mount du namespace du processus. + +## `internal/netif` + ```go -CreateNetns("netns-1") -CreateBridge("br-subnet-1", "/var/run/netns/netns-1") -CreateBridge("br-vx-1", "") // root -LinkBridgeNetns("veth1", "veth1-peer", "/var/run/netns/netns-1", "br-vx-1", "br-subnet-1") -AddIPToBridge("/var/run/netns/netns-1", "br-subnet-1", "10.0.1.1/32") -AddLinkLocalRoute("/var/run/netns/netns-1") +func CreateBridge(name string, mtu int) error +func BridgeSetMaster(iface, bridge string) error +func CreateVethToNetns(rootIf, nsIf, netnsPath string, mtu int) error +func CreateVxlan(name string, vxlanID int, localIface string, mtu int) error +func CreateTap(tapID int, bridgeName, vpcName string) error +func DeleteTap(tapID int, vpcName string) error +func DeleteLink(name string) error +func AddrAdd(iface string, ip net.IP) error +func RouteAdd(iface string, subnet *net.IPNet) error +func LinkSetUp(name string) error +func LinkSetDown(name string) error ``` -# systemd pour dnsmasq et metadata +**Conventions de nommage :** -Oui, c’est **tout à fait possible** avec la bibliothèque `go-systemd`, en particulier via le **module `dbus`**, qui permet de démarrer, arrêter et interroger l'état des unités `systemd`. +| Interface | Nom | Emplacement | +|---|---|---| +| Bridge subnet | `br-` | root + netns VPC | +| Veth subnet externe | `v--e` | root | +| Veth subnet interne | `v--i` | netns VPC | +| Veth VPC externe | `vp--e` | root | +| Veth VPC interne | `vp--i` | netns VPC | +| VXLAN | `vxlan-` | root | +| TAP VM | `tap` | netns VPC | +| Bridge public VPC | `br-public` | netns VPC | -Voici un exemple minimal de code Go pour **démarrer** et **arrêter** une unité comme `dnsmasq@vpc-00003_br-00000.service` : +**Paramètres réseau :** MTU 1500 pour les subnets, MTU 9000 pour les VPCs. VXLAN port 4789, learning désactivé. ---- +## Création d'un subnet -### ✅ Exemple de code pour start/stop via `go-systemd`: +`internal/subnet.CreateSubnet(db, subnetName)` : + +1. Crée la paire veth `v--{e,i}` vers le netns VPC +2. Crée `br-` (root) et `br-` (netns VPC) +3. Crée `vxlan-` dans root +4. Attache les interfaces à leurs bridges (`BridgeSetMaster`) +5. Active toutes les interfaces (`LinkSetUp`) +6. Ajoute l'adresse gateway sur `br-` dans le VPC +7. Ajoute une route `scope link` vers le CIDR du subnet sur `br-` dans le VPC +8. Applique les règles ebtables (ARP + DHCP DROP) +9. Génère la config dnsmasq → `/etc/dnsmasq.d/_.conf` +10. Démarre `dnsmasq@_.service` via systemd + +Suppression : ordre inverse — arrêt dnsmasq, suppression config, suppression règles ebtables, suppression des interfaces. + +## Création d'un VPC + +`internal/vpc.CreateVPC(db, vpcName)` : + +1. Crée le netns `` +2. Crée la paire veth `vp--{e,i}` +3. Crée `br-public` dans le netns VPC +4. Attache les veths à `br-public` +5. Active les interfaces + +Suppression : supprime `vp--e` (la paire disparaît) puis supprime le netns. + +## DHCP (`internal/dhcp`) + +dnsmasq est utilisé en mode static : chaque IP du subnet se voit attribuer une entrée `dhcp-host=,`. ```go -package main - -import ( - "context" - "fmt" - "log" - "time" - - "github.com/coreos/go-systemd/v22/dbus" -) - -func main() { - conn, err := dbus.NewSystemdConnectionContext(context.Background()) - if err != nil { - log.Fatalf("Erreur de connexion à systemd: %v", err) - } - defer conn.Close() - - unitName := "dnsmasq@vpc-00003_br-00000.service" - - // Démarrer l'unité - jobID, err := conn.StartUnitContext(context.Background(), unitName, "replace", nil) - if err != nil { - log.Fatalf("Erreur au démarrage de l'unité: %v", err) - } - fmt.Printf("Service démarré (job ID: %s)\n", jobID) - - time.Sleep(3 * time.Second) // attendre un peu - - // Arrêter l'unité - jobID, err = conn.StopUnitContext(context.Background(), unitName, "replace", nil) - if err != nil { - log.Fatalf("Erreur à l'arrêt de l'unité: %v", err) - } - fmt.Printf("Service arrêté (job ID: %s)\n", jobID) -} +func GenerateConfig(c Config) (string, map[string]string, error) +func StoreDHCPEntries(db *badger.DB, subnetName string, entries map[string]string) error +func GetMACForIP(db *badger.DB, subnetName, ip string) (string, error) ``` ---- +Config générée dans `/etc/dnsmasq.d/_.conf`. Entrées stockées en DB sous `subnet//dhcp/`. -### 🔐 Privilèges requis : +## Sécurité réseau -* Il faut que votre programme Go soit exécuté avec des **droits suffisants** (souvent `sudo`) pour interagir avec systemd **au niveau système**. +### ebtables (`internal/ebtables`) -Exemple d'exécution : +Appliquées sur chaque bridge subnet : -```bash -sudo ./mon-binaire-go +```go +func DropARPToGateway(bridge, gatewayIP string) error // DROP ARP Request vers la gateway +func DropDHCP(bridge string) error // DROP DHCP (ports 67:68) +func DeleteARPToGateway(bridge, gatewayIP string) error +func DeleteDHCP(bridge string) error ``` ---- +Empêche les VMs de résoudre ou d'usurper la gateway, et d'émettre leur propre DHCP. -### 📦 Dépendance : +### iptables (`internal/iptables`) -Ajoutez ce module dans votre `go.mod` : +Redirection metadata cloud-init : -```bash -go get github.com/coreos/go-systemd/v22 +```go +func AddMetadataRedirect(vmIP, gatewayIP, metadataPort string) error +func DeleteMetadataRedirect(vmIP, gatewayIP, metadataPort string) error ``` ---- +Redirige ` → 169.254.169.254:80` vers `:` via DNAT (table nat, PREROUTING). -Souhaitez-vous aussi vérifier l’état de l’unité (`Active`, `Failed`, etc.) ou juste la gestion start/stop ? +## systemd (`pkg/systemd`) + +```go +func New() (*Manager, error) +func (m *Manager) Start(service string) error +func (m *Manager) Stop(service string) error +func (m *Manager) Status(service string) (*ServiceStatus, error) +``` + +Utilise D-Bus (`coreos/go-systemd/v22`). Timeout job : 30 secondes. Service type : `"replace"`. + +Services gérés : `dnsmasq@_.service` (un par subnet). diff --git a/Agent Persistance.md b/Agent Persistance.md index e5c3c36..6952b9d 100644 --- a/Agent Persistance.md +++ b/Agent Persistance.md @@ -1,98 +1,68 @@ -# Persistance des data +# Persistance des données -```go -package vm +L'agent utilise **BadgerDB** (KV store embarqué) pour persister l'état de toutes les ressources. -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" -) +Chemin par défaut : `/var/lib/two/data/` -const defaultStateDir = "/var/lib/cloud-agent/vms" - -type VMInstance struct { - ID string - Pid int - QMPSocket string - HMPSocket string -} - -func (vm *VMInstance) SaveState(stateDir string) error { - if stateDir == "" { - stateDir = defaultStateDir - } - if err := os.MkdirAll(stateDir, 0755); err != nil { - return err - } - - file := filepath.Join(stateDir, vm.ID+".json") - data, err := json.MarshalIndent(vm, "", " ") - if err != nil { - return err - } - return os.WriteFile(file, data, 0644) -} - -func LoadVMState(path string) (*VMInstance, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - var vm VMInstance - if err := json.Unmarshal(data, &vm); err != nil { - return nil, err - } - return &vm, nil -} - -func LoadAllVMs(stateDir string) ([]*VMInstance, error) { - if stateDir == "" { - stateDir = defaultStateDir - } - files, err := filepath.Glob(filepath.Join(stateDir, "*.json")) - if err != nil { - return nil, err - } - - var vms []*VMInstance - for _, file := range files { - vm, err := LoadVMState(file) - if err != nil { - fmt.Printf("⚠️ Erreur chargement VM %s: %v\n", file, err) - continue - } - vms = append(vms, vm) - } - return vms, nil -} +Configurable via YAML : +```yaml +database: + path: "/var/lib/two/data/" ``` -```go -vm, _ := StartVM(cfg) -_ = vm.SaveState("") // "" = default dir -vm.StartMonitorLoop() +## Package `pkg/db/kv` + +| Fonction | Signature | Comportement | +|---|---|---| +| `InitDB` | `InitDB(conf Config, readonly bool) *badger.DB` | Ouvre la base (supporte mode lecture seule) | +| `AddInDB` | `AddInDB(db *badger.DB, key, value string) error` | Écrit ou écrase une clé | +| `GetFromDB` | `GetFromDB(db *badger.DB, key string) (string, error)` | Lit une clé, retourne `badger.ErrKeyNotFound` si absente | +| `DeleteInDB` | `DeleteInDB(db *badger.DB, key string) error` | Supprime récursivement une clé et tous ses sous-préfixes | +| `ListByPrefix` | `ListByPrefix(db *badger.DB, prefix string) (map[string]string, error)` | Liste toutes les clés commençant par un préfixe | + +Un serveur HTTP d'inspection est disponible via `kv.NewAdminServer` (`GET /db?prefix=...`). + +## Structure des clés + +### VPC +``` +vpc//state → "creating" | "created" | "deleting" | "deleted" ``` -#### Restaurer toutes les VMs au démarrage : - -```go -func RestoreVMsOnStartup() { - vms, err := LoadAllVMs("") - if err != nil { - log.Fatalf("Failed to load saved VMs: %v", err) - } - for _, vm := range vms { - if vm.IsAlive() { - vm.StartMonitorLoop() - log.Printf("✅ Reattached to running VM: %s (PID %d)", vm.ID, vm.Pid) - } else { - log.Printf("⚠️ VM %s is dead, needs cleanup or recovery", vm.ID) - } - } -} +### Subnet +``` +subnet//state → "creating" | "created" | "deleting" | "deleted" +subnet//vpc → +subnet//vxlan_id → +subnet//local_iface → +subnet//gateway_ip → +subnet//cidr → ``` -il va manquer une fonction `RemoveState(vmID)` après un `CleanupVM` \ No newline at end of file +### VM +``` +vm//state → "starting" | "started" | "stopping" | "stopped" +vm//subnet → +vm//ip → +vm//metadata_port → +vm//volume_path → +vm//memory → +vm//cpus → +vm//tap_id → +vm//password → (optionnel) +vm//sshkey → (optionnel) +``` + +## Cycle de vie d'une VM + +**Démarrage** (`StartVMCommand`) : +1. `Prepare` : vérifie que la VM n'existe pas → écrit toutes les clés avec état `"starting"` +2. `Execute` : attend que le subnet soit `"created"` → lance la VM → passe à `"started"` + +**Arrêt** (`StopVMCommand`) : +1. `Prepare` : vérifie que la VM existe → passe à `"stopping"` +2. `Execute` : arrête la VM → passe à `"stopped"` → `DeleteInDB("vm/")` (supprime toutes les sous-clés) + +## États transitoires et pannes + +Il n'y a pas de restauration automatique au démarrage. Si l'agent s'arrête pendant une opération, la base conserve l'état intermédiaire (ex: `"starting"`, `"creating"`). Une intervention externe (appel API) est nécessaire pour nettoyer ou relancer. diff --git a/Event Bus.md b/Event Bus.md index d95e10f..0c61a2a 100644 --- a/Event Bus.md +++ b/Event Bus.md @@ -1,129 +1,99 @@ -# A simple event queue +# Dispatcher & Queue de commandes -## 🧱 Design simple en Go +L'agent traite les opérations (création/suppression de VPC, subnet, VM) via un pattern +**Command** + **Dispatcher** + **Queue de workers**. -### 🎛️ Structure `Event` - -```go -type EventType string - -const ( - EventStart EventType = "start" - EventStop EventType = "stop" - EventReboot EventType = "reboot" - EventAttach EventType = "attach-disk" - EventDetach EventType = "detach-disk" -) - -type VMEvent struct { - Type EventType - VMID string - Params map[string]string // optionnel selon le type -} -``` - ---- - -### 📬 EventQueue (thread-safe FIFO + notify) - -```go -type EventQueue struct { - queue []VMEvent - lock sync.Mutex - signal chan struct{} -} - -func NewEventQueue() *EventQueue { - return &EventQueue{ - queue: make([]VMEvent, 0), - signal: make(chan struct{}, 1), - } -} - -func (eq *EventQueue) Push(ev VMEvent) { - eq.lock.Lock() - defer eq.lock.Unlock() - eq.queue = append(eq.queue, ev) - - select { - case eq.signal <- struct{}{}: - default: - // si déjà notify, pas besoin de spammer - } -} - -func (eq *EventQueue) Pop() (VMEvent, bool) { - eq.lock.Lock() - defer eq.lock.Unlock() - - if len(eq.queue) == 0 { - return VMEvent{}, false - } - - ev := eq.queue[0] - eq.queue = eq.queue[1:] - return ev, true -} -``` - ---- - -### 🔁 Boucle d'exécution FIFO - -```go -func StartEventLoop(eq *EventQueue, manager *Manager) { - go func() { - for { - <-eq.signal // bloquant jusqu'à ce qu’un event arrive - - for { - ev, ok := eq.Pop() - if !ok { - break - } - - log.Printf("⏳ Processing event %s for VM %s", ev.Type, ev.VMID) - - switch ev.Type { - case EventStart: - manager.StartVMByID(ev.VMID) - case EventStop: - manager.StopVMByID(ev.VMID) - case EventAttach: - manager.AttachDisk(ev.VMID, ev.Params["path"]) - case EventDetach: - manager.DetachDisk(ev.VMID, ev.Params["path"]) - default: - log.Printf("⚠️ Unknown event type: %s", ev.Type) - } - } - } - }() -} -``` - ---- - -### 🌐 Exposition via HTTP (exemple minimal) - -```go -func handleStartVM(w http.ResponseWriter, r *http.Request, eq *EventQueue) { - vmID := r.URL.Query().Get("id") - if vmID == "" { - http.Error(w, "missing id", 400) - return - } - eq.Push(VMEvent{Type: EventStart, VMID: vmID}) - fmt.Fprintf(w, "Start request enqueued") -} -``` - ---- - -## 🔔 Résumé de la comm' inter-composants +## Vue d'ensemble ```text -[API HTTP] --(Push VMEvent)--> [EventQueue] --(notify chan)--> [EventLoop] - | - [Pop / Switch exec based on event type] -``` \ No newline at end of file +[Handler HTTP] + │ + ├── Prepare(cmd) → validation + écriture état initial en DB (synchrone) + │ retourne 4xx si la validation échoue + │ + ├── Dispatch(cmd) → enfile cmd.Execute() dans la queue (asynchrone) + │ + └── 202 Accepted → retourné immédiatement au client + +[Worker goroutine] + └── consomme la queue → exécute cmd.Execute() +``` + +## Interface `Command` + +```go +type Command interface { + Prepare(db *badger.DB, cfg *configuration.Config) error + Execute(db *badger.DB, cfg *configuration.Config) error +} +``` + +- **`Prepare`** : valide les prérequis, écrit l'état initial en DB (`"creating"`, `"starting"`…) +- **`Execute`** : effectue l'opération réelle (appels système, réseau, QEMU…) + +## Queue (`pkg/worker`) + +```go +type Task func() + +type Queue struct { tasks chan Task } + +func New(bufferSize int) *Queue +func (q *Queue) Submit(t Task) // enfile une tâche +func (q *Queue) Start(n int) // lance n goroutines workers +``` + +La taille du buffer et le nombre de workers sont configurables : + +```yaml +worker: + buffer_size: 1000 + count: 4 +``` + +## Dispatcher (`internal/dispatcher/agent`) + +```go +type Dispatcher struct { /* queue, db, cfg, logger */ } + +func (d *Dispatcher) Prepare(cmd Command) error // appelle cmd.Prepare() +func (d *Dispatcher) Dispatch(cmd Command) // enfile cmd.Execute() + log durée +``` + +## Commandes disponibles + +| Commande | Fichier | États écrits | +|---|---|---| +| `CreateVPCCommand` | `vpc_commands.go` | `creating` → `created` | +| `DeleteVPCCommand` | `vpc_commands.go` | `deleting` → `deleted` | +| `CreateSubnetCommand` | `subnet_commands.go` | `creating` → `created` | +| `DeleteSubnetCommand` | `subnet_commands.go` | `deleting` → `deleted` | +| `StartVMCommand` | `vm_commands.go` | `starting` → `started` | +| `StopVMCommand` | `vm_commands.go` | `stopping` → `stopped` | + +## Dépendances entre commandes + +Certaines commandes attendent via polling que leurs dépendances soient prêtes : + +- `StartVMCommand.Execute` attend `subnet//state == "created"` +- `DeleteVPCCommand.Execute` attend que tous ses subnets soient `"deleted"` + +Le timeout est configurable : + +```yaml +dispatcher: + timeout_seconds: 120 + poll_seconds: 2 +``` + +## Exemple de flux : POST /vpcs + +```text +1. Handler parse la requête → crée CreateVPCCommand{Name: "my-vpc"} +2. Prepare : vérifie que "vpc/my-vpc/state" n'existe pas + écrit "vpc/my-vpc/state" = "creating" +3. Dispatch : enfile Execute() dans la queue +4. Handler : retourne 202 {"name": "my-vpc", "state": "creating"} +5. Worker : exécute vpc.CreateVPC(db, "my-vpc") + passe l'état à "created" +``` diff --git a/_Sidebar.md b/_Sidebar.md index a47bf0d..7e30168 100644 --- a/_Sidebar.md +++ b/_Sidebar.md @@ -8,4 +8,4 @@ - [Metadata](./Agent%20Metadata.md) - [Persistance](./Agent%20Persistance.md) - [HTTP](./Agent%20HTTP.md) -- [Event BUS](./Event%20BUS.md) \ No newline at end of file + - [Event BUS](./Event%20BUS.md) diff --git a/vnc.md b/vnc.md new file mode 100644 index 0000000..fff6217 --- /dev/null +++ b/vnc.md @@ -0,0 +1,183 @@ +# 📄 1. Résumé architecture (Markdown) + +```markdown +# Architecture VNC over WebSocket avec multiplexage Unix socket + +## Vue d’ensemble + +Cette architecture permet d’exposer des consoles VNC de machines virtuelles QEMU via WebSocket (compatible noVNC), en utilisant des sockets Unix pour renforcer la sécurité et simplifier le réseau. + +``` + +[ Browser (noVNC) ] +↓ HTTPS / WebSocket +[ Caddy (Global, piloté par orchestrateur) ] +↓ +[ VNC Gateway (par host) ] +↓ +[ Unix socket VNC ] +↓ +[ VM QEMU ] + +```` + +--- + +## Composants + +### 1. QEMU (serveur VNC) + +Chaque VM expose son interface VNC via un socket Unix : + +```bash +qemu-system-x86_64 -vnc unix:/run/vnc/.sock +```` + +Caractéristiques : + +* Aucun port TCP exposé +* Isolation forte par VM +* Accès restreint via permissions filesystem + +--- + +### 2. Organisation des sockets + +``` +/run/vnc/ + ├── vm-1.sock + ├── vm-2.sock + └── vm-3.sock +``` + +Le nom du fichier correspond directement à l’identifiant de la VM (`vm-id`). + +--- + +### 3. VNC Gateway (multiplexeur) + +Service unique par host. + +#### Fonction + +* Écoute sur un port TCP unique (ex: `:9000`) +* Accepte des connexions WebSocket +* Route dynamiquement vers le bon socket Unix + +#### Routing implicite + +``` +/vnc/ → /run/vnc/.sock +``` + +#### Flux + +``` +WebSocket ⇄ Gateway ⇄ Unix socket ⇄ VNC (QEMU) +``` + +--- + +### 4. Caddy (reverse proxy global) + +Configuré dynamiquement par l’orchestrateur. + +#### Rôle + +* Terminaison TLS +* Routage vers le bon host +* Basé sur le `vm-id` dans l’URL + +#### Exemple + +``` +https://example.com/vnc/vm-123 +``` + +→ routé vers : + +``` +http://host-A:9000 +``` + +--- + +### 5. Orchestrateur + +Responsable du mapping : + +``` +vm-id → host +``` + +Fonctions : + +* allocation des VMs +* suivi des hosts +* configuration dynamique de Caddy + +--- + +## Flux complet + +``` +1. Client ouvre noVNC +2. Connexion WebSocket : + wss://example.com/vnc/ + +3. Caddy : + → route vers le bon host + +4. Gateway : + → ouvre /run/vnc/.sock + +5. Communication VNC établie +``` + +--- + +## Sécurité + +### Niveau système + +* sockets Unix avec permissions restreintes +* aucun port VNC exposé + +### Gateway + +* validation stricte du `vm-id` +* prévention path traversal + +### Edge (Caddy) + +* TLS obligatoire +* authentification possible (JWT, session) + +--- + +## Avantages + +* **Pas de ports dynamiques** +* **Multiplexage efficace** +* **Isolation forte (Unix sockets)** +* **Scalabilité horizontale** +* **Simplicité réseau** +* **Compatibilité native avec noVNC** + +--- + +## Points de vigilance + +* nettoyage des sockets après arrêt VM +* gestion des timeouts WebSocket +* limites système (file descriptors) +* validation stricte des identifiants + +--- + +## Extension possibles + +* multi-tenant (`/run/vnc//.sock`) +* authentification intégrée gateway +* métriques (connexions actives) +* autoscaling des hosts