From b41b4f251802ae3afb59d593d3bab590c3e81f31 Mon Sep 17 00:00:00 2001 From: GnomeZworc Date: Mon, 18 May 2026 23:25:39 +0200 Subject: [PATCH] f-28: generate metadata_port automatically at vm creation Signed-off-by: GnomeZworc --- api/agent.yaml | 5 +-- internal/api/agent/models.go | 15 ++++---- internal/api/agent/vms.go | 21 +++++------ internal/dispatcher/agent/vm_commands.go | 47 +++++++++++++++++++----- 4 files changed, 55 insertions(+), 33 deletions(-) diff --git a/api/agent.yaml b/api/agent.yaml index 1476859..e75601d 100644 --- a/api/agent.yaml +++ b/api/agent.yaml @@ -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) diff --git a/internal/api/agent/models.go b/internal/api/agent/models.go index 6535c4c..dc0c47f 100644 --- a/internal/api/agent/models.go +++ b/internal/api/agent/models.go @@ -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 { diff --git a/internal/api/agent/vms.go b/internal/api/agent/vms.go index 841c429..95f2c85 100644 --- a/internal/api/agent/vms.go +++ b/internal/api/agent/vms.go @@ -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 { diff --git a/internal/dispatcher/agent/vm_commands.go b/internal/dispatcher/agent/vm_commands.go index aade998..1bb55d4 100644 --- a/internal/dispatcher/agent/vm_commands.go +++ b/internal/dispatcher/agent/vm_commands.go @@ -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 {