2 Agent Instance
GnomeZworc edited this page 2025-04-27 20:28:59 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Fonctionnement des vms

Structure de demande de creation de VM

type Volume struct {
	path      string `/ou/ce/trouve/le/fichier/qcow2`
	position  int    `[0-9]*`
}

type Network struct {
	vxlanid     int    `0-2000000`
	netname     string `vpc-[0-1,a-z]10`
	subnetname  string `subnet-[0-1,a-z]10`
	position    int    `[0-9]*`
	ipV4        string
	gatewayV4   string
	ipV6        string
	gatewayV6   string
}

type NetRule struct {
	proto        string
	port         int
	source       string
	destination  string
}

type VmConfig struct {
	Id        string    `i-[0-9]10`
	Volumes   Volume[]
	Networks  Network[]
	MemoryMB  int
	CPUs      int
	Rules     NetRule[]
	Keys      string[]
}

Structure de gestion des ressources

vm_list
  volume_list
  metadata_server
  network_id_info
subnet_list
  vm_id_list

ordre d'execution

Create_VM:
  add vm in list, state pending
  if subnet do not exist:    <- does not exist if not il list
    if network do not exist: <- does not exist if we do not have a subnet in it
	  add network in list, state pending
	  create netns
	create veth
	create internal_bridge
	set network, state created
  create tap
  launch metadata/dhcp server
  if qcow2 file does not exist <- simple file check
  	create qcow2 files
  start vm
  set vm on running

Delete_VM:
  stop vm
  stop metadata/dhcp server
  remove tap
  if last vm in subnet
    delete subnet
	delete veth
    if last subnet in net
      delete net
	remove subnet from list
  remove vm form list

Demarrage

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,index=0", 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 lagent
	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

// 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

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
}
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)
	}
}