f-28: generate metadata_port automatically at vm creation

Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
GnomeZworc 2026-05-18 23:25:39 +02:00
commit b41b4f2518
Signed by: nicolas.boufideline
GPG key ID: 4406BBBF8845D632
4 changed files with 55 additions and 33 deletions

View file

@ -402,14 +402,11 @@ components:
VMCreateRequest: VMCreateRequest:
type: object type: object
required: [name, metadata_port, interfaces, storage] required: [name, interfaces, storage]
properties: properties:
name: name:
type: string type: string
example: vm-00001 example: vm-00001
metadata_port:
type: string
example: "80"
memory: memory:
type: integer type: integer
description: Memory in MB (default 512) description: Memory in MB (default 512)

View file

@ -46,14 +46,13 @@ type VMStorage struct {
} }
type VMCreateRequest struct { type VMCreateRequest struct {
Name string `json:"name"` Name string `json:"name"`
MetadataPort string `json:"metadata_port"` Memory int `json:"memory"`
Memory int `json:"memory"` CPUs int `json:"cpus"`
CPUs int `json:"cpus"` Password string `json:"password"`
Password string `json:"password"` SSHKey string `json:"sshkey"`
SSHKey string `json:"sshkey"` Interfaces []VMInterface `json:"interfaces"`
Interfaces []VMInterface `json:"interfaces"` Storage []VMStorage `json:"storage"`
Storage []VMStorage `json:"storage"`
} }
type VM struct { type VM struct {

View file

@ -58,9 +58,9 @@ func (s *Server) startVM(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(ErrorResponse{Error: "invalid request body"}) json.NewEncoder(w).Encode(ErrorResponse{Error: "invalid request body"})
return return
} }
if req.Name == "" || req.MetadataPort == "" || len(req.Interfaces) == 0 || len(req.Storage) == 0 { if req.Name == "" || len(req.Interfaces) == 0 || len(req.Storage) == 0 {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(ErrorResponse{Error: "name, metadata_port, interfaces and storage are required"}) json.NewEncoder(w).Encode(ErrorResponse{Error: "name, interfaces and storage are required"})
return return
} }
@ -78,15 +78,14 @@ func (s *Server) startVM(w http.ResponseWriter, r *http.Request) {
} }
cmd := dispatcher.StartVMCommand{ cmd := dispatcher.StartVMCommand{
Name: req.Name, Name: req.Name,
Subnet: primary.Subnet, Subnet: primary.Subnet,
IP: primary.IP, IP: primary.IP,
MetadataPort: req.MetadataPort, VolumePath: req.Storage[0].Path,
VolumePath: req.Storage[0].Path, Memory: req.Memory,
Memory: req.Memory, CPUs: req.CPUs,
CPUs: req.CPUs, Password: req.Password,
Password: req.Password, SSHKey: req.SSHKey,
SSHKey: req.SSHKey,
} }
if err := s.dispatcher.Prepare(cmd); err != nil { if err := s.dispatcher.Prepare(cmd); err != nil {

View file

@ -2,7 +2,9 @@ package dispatcher
import ( import (
"fmt" "fmt"
"math/rand"
"strconv" "strconv"
"strings"
"time" "time"
configuration "git.g3e.fr/syonad/two/internal/config/agent" configuration "git.g3e.fr/syonad/two/internal/config/agent"
@ -12,15 +14,14 @@ import (
) )
type StartVMCommand struct { type StartVMCommand struct {
Name string Name string
Subnet string Subnet string
IP string IP string
MetadataPort string VolumePath string
VolumePath string Memory int
Memory int CPUs int
CPUs int Password string
Password string SSHKey string
SSHKey string
} }
func (c StartVMCommand) Prepare(db *badger.DB, _ *configuration.Config) error { func (c StartVMCommand) Prepare(db *badger.DB, _ *configuration.Config) error {
@ -34,10 +35,14 @@ func (c StartVMCommand) Prepare(db *badger.DB, _ *configuration.Config) error {
if subnetState == "deleting" || subnetState == "deleted" { if subnetState == "deleting" || subnetState == "deleted" {
return fmt.Errorf("subnet %q is %s", c.Subnet, subnetState) return fmt.Errorf("subnet %q is %s", c.Subnet, subnetState)
} }
port, err := allocateMetadataPort(db)
if err != nil {
return fmt.Errorf("allocate metadata port: %w", err)
}
kv.AddInDB(db, "vm/"+c.Name+"/state", "starting") kv.AddInDB(db, "vm/"+c.Name+"/state", "starting")
kv.AddInDB(db, "vm/"+c.Name+"/subnet", c.Subnet) kv.AddInDB(db, "vm/"+c.Name+"/subnet", c.Subnet)
kv.AddInDB(db, "vm/"+c.Name+"/ip", c.IP) kv.AddInDB(db, "vm/"+c.Name+"/ip", c.IP)
kv.AddInDB(db, "vm/"+c.Name+"/metadata_port", c.MetadataPort) kv.AddInDB(db, "vm/"+c.Name+"/metadata_port", strconv.Itoa(port))
kv.AddInDB(db, "vm/"+c.Name+"/volume_path", c.VolumePath) kv.AddInDB(db, "vm/"+c.Name+"/volume_path", c.VolumePath)
kv.AddInDB(db, "vm/"+c.Name+"/memory", strconv.Itoa(c.Memory)) kv.AddInDB(db, "vm/"+c.Name+"/memory", strconv.Itoa(c.Memory))
kv.AddInDB(db, "vm/"+c.Name+"/cpus", strconv.Itoa(c.CPUs)) kv.AddInDB(db, "vm/"+c.Name+"/cpus", strconv.Itoa(c.CPUs))
@ -50,6 +55,28 @@ func (c StartVMCommand) Prepare(db *badger.DB, _ *configuration.Config) error {
return nil return nil
} }
func allocateMetadataPort(db *badger.DB) (int, error) {
entries, err := kv.ListByPrefix(db, "vm/")
if err != nil {
return 0, err
}
used := make(map[int]struct{})
for key, value := range entries {
if strings.HasSuffix(key, "/metadata_port") {
if p, err := strconv.Atoi(value); err == nil {
used[p] = struct{}{}
}
}
}
for range 100 {
p := rand.Intn(9000) + 1000
if _, taken := used[p]; !taken {
return p, nil
}
}
return 0, fmt.Errorf("no free metadata port available in [1000, 9999]")
}
func (c StartVMCommand) Execute(db *badger.DB, cfg *configuration.Config) error { func (c StartVMCommand) Execute(db *badger.DB, cfg *configuration.Config) error {
timeout := time.After(time.Duration(cfg.Dispatcher.TimeoutSeconds) * time.Second) timeout := time.After(time.Duration(cfg.Dispatcher.TimeoutSeconds) * time.Second)
for { for {