add multiple data and architecture document
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
parent
0aa49e3cf6
commit
43f7cc86aa
5 changed files with 665 additions and 1 deletions
|
|
@ -28,3 +28,6 @@ Tout cela serait lancer depuis une api:
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- [Reseaux](./agent/Network.md)
|
- [Reseaux](./agent/Network.md)
|
||||||
|
- [Les vms](./agent/Instance.md)
|
||||||
|
- [Serveur http](./agent/http.md)
|
||||||
|
- [Persistance](./agent/Persistance.md)
|
||||||
245
agent/Instance.md
Normal file
245
agent/Instance.md
Normal file
|
|
@ -0,0 +1,245 @@
|
||||||
|
# Fonctionnement des vms
|
||||||
|
|
||||||
|
## Demarrage
|
||||||
|
|
||||||
|
```go
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VMConfig struct {
|
||||||
|
Name string
|
||||||
|
ImagePath string
|
||||||
|
TapIfName string
|
||||||
|
NetNS string
|
||||||
|
MemoryMB int
|
||||||
|
CPUs int
|
||||||
|
QMPSocket string
|
||||||
|
HMPSocket string
|
||||||
|
}
|
||||||
|
|
||||||
|
type VMInstance struct {
|
||||||
|
Pid int
|
||||||
|
QMPSocket string
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartVM(cfg VMConfig) (*VMInstance, error) {
|
||||||
|
qmpSock := cfg.QMPSocket
|
||||||
|
if qmpSock == "" {
|
||||||
|
qmpSock = fmt.Sprintf("/run/vms/%s.qmp", cfg.Name)
|
||||||
|
}
|
||||||
|
hmpSock := cfg.HMPSocket
|
||||||
|
if hmpSock == "" {
|
||||||
|
hmpSock = fmt.Sprintf("/run/vms/%s.sock", cfg.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure socket dir exists
|
||||||
|
if err := os.MkdirAll(filepath.Dir(qmpSock), 0755); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create qmp dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-name", cfg.Name,
|
||||||
|
"-m", fmt.Sprintf("%d", cfg.MemoryMB),
|
||||||
|
"-smp", fmt.Sprintf("%d", cfg.CPUs),
|
||||||
|
"-drive", fmt.Sprintf("file=%s,format=qcow2,if=virtio", cfg.ImagePath),
|
||||||
|
"-netdev", fmt.Sprintf("tap,id=net0,ifname=%s,script=no,downscript=no", cfg.TapIfName),
|
||||||
|
"-device", "virtio-net-pci,netdev=net0",
|
||||||
|
"-qmp", fmt.Sprintf("unix:%s,server,nowait", qmpSock),
|
||||||
|
"-monitor", fmt.Sprintf("unix:%s,server,nowait", hmpSockt),
|
||||||
|
"-nographic",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("ip", append([]string{"netns", "exec", cfg.NetNS, "qemu-system-x86_64"}, args...)...)
|
||||||
|
|
||||||
|
// Rediriger les logs vers le stdout/stderr de l’agent
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
err := cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to start qemu: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VMInstance{
|
||||||
|
Pid: cmd.Process.Pid,
|
||||||
|
QMPSocket: qmpSock,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arret
|
||||||
|
|
||||||
|
- `ShutdownVM(vmID string)`
|
||||||
|
→ envoie `system_powerdown` (soft shutdown)
|
||||||
|
- `ForceStopVM(vmID string)`
|
||||||
|
→ envoie `quit` (kill direct)
|
||||||
|
|
||||||
|
pour le check du status d'une VM
|
||||||
|
|
||||||
|
```go
|
||||||
|
// vm/monitor.go
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (vm *VMInstance) StartMonitorLoop() {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
// 🔍 Check si le process est zombie
|
||||||
|
state, err := readProcState(vm.Pid)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ VM %s: impossible de lire /proc: %v", vm.ID, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if state == "Z" {
|
||||||
|
log.Printf("💀 VM %s (PID %d) est en état zombie", vm.ID, vm.Pid)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🧠 Ping QMP socket
|
||||||
|
status, err := queryQMPStatus(vm.QMPSocket)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("⚠️ VM %s: QMP unreachable: %v", vm.ID, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("🟢 VM %s status: %s", vm.ID, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 📄 Lecture du statut dans /proc/<pid>/stat
|
||||||
|
func readProcState(pid int) (string, error) {
|
||||||
|
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
parts := strings.Split(string(data), " ")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return "", fmt.Errorf("invalid stat format")
|
||||||
|
}
|
||||||
|
return parts[2], nil // 3ème champ = state: R, S, D, Z...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 📡 Envoie "query-status" sur le QMP
|
||||||
|
func queryQMPStatus(socketPath string) (string, error) {
|
||||||
|
conn, err := net.Dial("unix", socketPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// Lire la bannière QMP
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(buf[:n]), `"QMP"`) {
|
||||||
|
return "", fmt.Errorf("invalid QMP banner")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envoyer query-status
|
||||||
|
cmd := `{"execute": "query-status"}` + "\n"
|
||||||
|
if _, err := conn.Write([]byte(cmd)); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lire la réponse
|
||||||
|
n, err = conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp map[string]interface{}
|
||||||
|
if err := json.Unmarshal(buf[:n], &resp); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if returnVal, ok := resp["return"].(map[string]interface{}); ok {
|
||||||
|
if status, ok := returnVal["status"].(string); ok {
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("unable to parse QMP status")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## check des zombies
|
||||||
|
|
||||||
|
```go
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DetectZombieVMs(knownVMs map[int]bool) ([]int, error) {
|
||||||
|
entries, err := os.ReadDir("/proc")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var zombies []int
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
if !e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pid, err := strconv.Atoi(e.Name())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdlinePath := filepath.Join("/proc", e.Name(), "cmdline")
|
||||||
|
data, err := os.ReadFile(cmdlinePath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(string(data), "qemu-system") {
|
||||||
|
if !knownVMs[pid] {
|
||||||
|
zombies = append(zombies, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return zombies, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
func CheckForZombies(vmList []*VMInstance) {
|
||||||
|
known := make(map[int]bool)
|
||||||
|
for _, vm := range vmList {
|
||||||
|
known[vm.Pid] = true
|
||||||
|
}
|
||||||
|
zombies, err := DetectZombieVMs(known)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Erreur check zombies: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, pid := range zombies {
|
||||||
|
log.Printf("🧟 VM zombie détectée: PID %d (non gérée par l'agent)", pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
160
agent/Network.md
160
agent/Network.md
|
|
@ -32,3 +32,163 @@
|
||||||
+-----------------------+ +-----------------------+
|
+-----------------------+ +-----------------------+
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```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)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```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")
|
||||||
|
```
|
||||||
98
agent/Persistance.md
Normal file
98
agent/Persistance.md
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Persistance des data
|
||||||
|
|
||||||
|
```go
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
vm, _ := StartVM(cfg)
|
||||||
|
_ = vm.SaveState("") // "" = default dir
|
||||||
|
vm.StartMonitorLoop()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
il va manquer une fonction `RemoveState(vmID)` après un `CleanupVM`
|
||||||
158
agent/http.md
Normal file
158
agent/http.md
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
# Async http server
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------- Types JSON-RPC -----------
|
||||||
|
|
||||||
|
type JSONRPCRequest struct {
|
||||||
|
JSONRPC string `json:"jsonrpc"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params json.RawMessage `json:"params"`
|
||||||
|
ID interface{} `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSONRPCResponse struct {
|
||||||
|
JSONRPC string `json:"jsonrpc"`
|
||||||
|
Result interface{} `json:"result,omitempty"`
|
||||||
|
Error interface{} `json:"error,omitempty"`
|
||||||
|
ID interface{} `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
Method string
|
||||||
|
Params json.RawMessage
|
||||||
|
ID interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------- Routine d'exécution -----------
|
||||||
|
|
||||||
|
func execRoutine(taskQueue <-chan Task, resultChan chan<- JSONRPCResponse) {
|
||||||
|
for task := range taskQueue {
|
||||||
|
var result interface{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch task.Method {
|
||||||
|
case "StartVM":
|
||||||
|
result, err = handleStartVM(task.Params)
|
||||||
|
case "StopVM":
|
||||||
|
result, err = handleStopVM(task.Params)
|
||||||
|
case "AttachDisk":
|
||||||
|
result, err = handleAttachDisk(task.Params)
|
||||||
|
case "RestoreVMs":
|
||||||
|
result, err = handleRestoreVMs()
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("méthode inconnue : %s", task.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := JSONRPCResponse{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: task.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
res.Error = map[string]interface{}{
|
||||||
|
"code": -32601,
|
||||||
|
"message": err.Error(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.Result = result
|
||||||
|
}
|
||||||
|
|
||||||
|
resultChan <- res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------- Fonctions simulées -----------
|
||||||
|
|
||||||
|
func handleStartVM(params json.RawMessage) (string, error) {
|
||||||
|
var args []string
|
||||||
|
if err := json.Unmarshal(params, &args); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
log.Println("→ StartVM", args)
|
||||||
|
return "VM démarrée : " + fmt.Sprint(args), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStopVM(params json.RawMessage) (string, error) {
|
||||||
|
var args []string
|
||||||
|
if err := json.Unmarshal(params, &args); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
log.Println("→ StopVM", args)
|
||||||
|
return "VM arrêtée : " + fmt.Sprint(args), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAttachDisk(params json.RawMessage) (string, error) {
|
||||||
|
var args []string
|
||||||
|
if err := json.Unmarshal(params, &args); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
log.Println("→ AttachDisk", args)
|
||||||
|
return "Disque attaché : " + fmt.Sprint(args), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRestoreVMs() (string, error) {
|
||||||
|
log.Println("→ RestoreVMs")
|
||||||
|
return "VMs restaurées", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------- Serveur JSON-RPC -----------
|
||||||
|
|
||||||
|
func rpcHandler(taskQueue chan<- Task, resultChan <-chan JSONRPCResponse) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Méthode non autorisée", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Erreur lecture corps", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req JSONRPCRequest
|
||||||
|
if err := json.Unmarshal(body, &req); err != nil {
|
||||||
|
http.Error(w, "JSON invalide", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
taskQueue <- Task{
|
||||||
|
Method: req.Method,
|
||||||
|
Params: req.Params,
|
||||||
|
ID: req.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
res := <-resultChan
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------- Main -----------
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Println("== Agent Go JSON-RPC ==")
|
||||||
|
|
||||||
|
taskQueue := make(chan Task, 100)
|
||||||
|
resultChan := make(chan JSONRPCResponse, 100)
|
||||||
|
|
||||||
|
go execRoutine(taskQueue, resultChan)
|
||||||
|
|
||||||
|
http.HandleFunc("/rpc", rpcHandler(taskQueue, resultChan))
|
||||||
|
|
||||||
|
log.Println("Serveur JSON-RPC sur :8080")
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
```
|
||||||
Loading…
Add table
Add a link
Reference in a new issue