feature-28 #30

Manually merged
nicolas.boufideline merged 18 commits from feature-28 into main 2026-05-23 20:43:51 +00:00
4 changed files with 55 additions and 33 deletions
Showing only changes of commit b41b4f2518 - Show all commits

f-28: generate metadata_port automatically at vm creation

Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
GnomeZworc 2026-05-18 23:25:39 +02:00
Signed by: nicolas.boufideline
GPG key ID: 4406BBBF8845D632

View file

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

View file

@ -46,14 +46,13 @@ type VMStorage struct {
}
type VMCreateRequest struct {
Name string `json:"name"`
MetadataPort string `json:"metadata_port"`
Memory int `json:"memory"`
CPUs int `json:"cpus"`
Password string `json:"password"`
SSHKey string `json:"sshkey"`
Interfaces []VMInterface `json:"interfaces"`
Storage []VMStorage `json:"storage"`
Name string `json:"name"`
Memory int `json:"memory"`
CPUs int `json:"cpus"`
Password string `json:"password"`
SSHKey string `json:"sshkey"`
Interfaces []VMInterface `json:"interfaces"`
Storage []VMStorage `json:"storage"`
}
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"})
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)
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
}
@ -78,15 +78,14 @@ func (s *Server) startVM(w http.ResponseWriter, r *http.Request) {
}
cmd := dispatcher.StartVMCommand{
Name: req.Name,
Subnet: primary.Subnet,
IP: primary.IP,
MetadataPort: req.MetadataPort,
VolumePath: req.Storage[0].Path,
Memory: req.Memory,
CPUs: req.CPUs,
Password: req.Password,
SSHKey: req.SSHKey,
Name: req.Name,
Subnet: primary.Subnet,
IP: primary.IP,
VolumePath: req.Storage[0].Path,
Memory: req.Memory,
CPUs: req.CPUs,
Password: req.Password,
SSHKey: req.SSHKey,
}
if err := s.dispatcher.Prepare(cmd); err != nil {

View file

@ -2,7 +2,9 @@ package dispatcher
import (
"fmt"
"math/rand"
"strconv"
"strings"
"time"
configuration "git.g3e.fr/syonad/two/internal/config/agent"
@ -12,15 +14,14 @@ import (
)
type StartVMCommand struct {
Name string
Subnet string
IP string
MetadataPort string
VolumePath string
Memory int
CPUs int
Password string
SSHKey string
Name string
Subnet string
IP string
VolumePath string
Memory int
CPUs int
Password string
SSHKey string
}
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" {
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+"/subnet", c.Subnet)
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+"/memory", strconv.Itoa(c.Memory))
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
}
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 {
timeout := time.After(time.Duration(cfg.Dispatcher.TimeoutSeconds) * time.Second)
for {