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
|
|
@ -27,4 +27,7 @@ Tout cela serait lancer depuis une api:
|
|||
|
||||
## 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
|
|
@ -31,4 +31,164 @@
|
|||
| (Tunnel inter-host) | | (Tunnel inter-host) |
|
||||
+-----------------------+ +-----------------------+
|
||||
|
||||
```
|
||||
|
||||
```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