f-34: metadata: update api #34
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
ae81158b2d
commit
e2fbb5fb7a
3 changed files with 144 additions and 11 deletions
|
|
@ -45,13 +45,18 @@ type VMStorage struct {
|
||||||
Dev string `json:"dev"`
|
Dev string `json:"dev"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VMMetadata struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
SSHKey string `json:"sshkey"`
|
||||||
|
UserData string `json:"user_data"`
|
||||||
|
}
|
||||||
|
|
||||||
type VMCreateRequest struct {
|
type VMCreateRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Memory int `json:"memory"`
|
Memory int `json:"memory"`
|
||||||
CPUs int `json:"cpus"`
|
CPUs int `json:"cpus"`
|
||||||
UEFI bool `json:"uefi"`
|
UEFI bool `json:"uefi"`
|
||||||
Password string `json:"password"`
|
Metadata VMMetadata `json:"metadata"`
|
||||||
SSHKey string `json:"sshkey"`
|
|
||||||
Interfaces []VMInterface `json:"interfaces"`
|
Interfaces []VMInterface `json:"interfaces"`
|
||||||
Storage []VMStorage `json:"storage"`
|
Storage []VMStorage `json:"storage"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,16 @@ package agentapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.g3e.fr/syonad/two/pkg/db/kv"
|
"git.g3e.fr/syonad/two/pkg/db/kv"
|
||||||
|
"github.com/dgraph-io/badger/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- vmFromDB ---
|
// --- vmFromDB ---
|
||||||
|
|
@ -156,3 +159,106 @@ func TestStartVM_StorageReturnedInResponse(t *testing.T) {
|
||||||
t.Errorf("disque inattendu dans la réponse : %+v", vm.Storage[0])
|
t.Errorf("disque inattendu dans la réponse : %+v", vm.Storage[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- POST /vms : objet metadata ---
|
||||||
|
|
||||||
|
func postVM(t *testing.T, name string, meta VMMetadata) (*httptest.ResponseRecorder, *badger.DB) {
|
||||||
|
t.Helper()
|
||||||
|
s, db := newTestServer(t)
|
||||||
|
kv.AddInDB(db, "subnet/sn-1/state", "running")
|
||||||
|
kv.AddInDB(db, "subnet/sn-1/vpc", "vpc-1")
|
||||||
|
|
||||||
|
body, _ := json.Marshal(VMCreateRequest{
|
||||||
|
Name: name,
|
||||||
|
Interfaces: []VMInterface{{Subnet: "sn-1", IP: "10.0.0.20", Primary: true}},
|
||||||
|
Storage: []VMStorage{{Path: "/data/root.qcow2", Dev: "vda"}},
|
||||||
|
Memory: 1024,
|
||||||
|
CPUs: 2,
|
||||||
|
Metadata: meta,
|
||||||
|
})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
s.VmsHandler(w, httptest.NewRequest(http.MethodPost, "/vms", bytes.NewReader(body)))
|
||||||
|
return w, db
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartVM_UserDataIsDecodedFromBase64(t *testing.T) {
|
||||||
|
plain := "#cloud-config\npackages:\n - nginx\n"
|
||||||
|
encoded := base64.StdEncoding.EncodeToString([]byte(plain))
|
||||||
|
|
||||||
|
w, db := postVM(t, "vm-md1", VMMetadata{UserData: encoded})
|
||||||
|
if w.Code != http.StatusAccepted {
|
||||||
|
t.Fatalf("attendu 202, obtenu %d : %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := kv.GetFromDB(db, "vm/vm-md1/metadata/user-data")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("user-data absent en DB : %v", err)
|
||||||
|
}
|
||||||
|
if got != plain {
|
||||||
|
t.Errorf("user-data décodé attendu %q, obtenu %q", plain, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartVM_InvalidBase64IsRejected(t *testing.T) {
|
||||||
|
w, db := postVM(t, "vm-md2", VMMetadata{UserData: "ceci n'est pas du base64 !!"})
|
||||||
|
|
||||||
|
if w.Code != http.StatusBadRequest {
|
||||||
|
t.Fatalf("un base64 invalide doit être refusé en 400, obtenu %d : %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
if _, err := kv.GetFromDB(db, "vm/vm-md2/state"); err == nil {
|
||||||
|
t.Error("aucune VM ne doit être créée quand la requête est refusée")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartVM_InvalidBase64ErrorNamesTheField(t *testing.T) {
|
||||||
|
w, _ := postVM(t, "vm-md3", VMMetadata{UserData: "@@@"})
|
||||||
|
|
||||||
|
if !strings.Contains(w.Body.String(), "user_data") {
|
||||||
|
t.Errorf("le message doit nommer le champ fautif : %s", w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartVM_NoUserDataWritesNoDocument(t *testing.T) {
|
||||||
|
w, db := postVM(t, "vm-md4", VMMetadata{SSHKey: "ssh-ed25519 AAAA user@host"})
|
||||||
|
if w.Code != http.StatusAccepted {
|
||||||
|
t.Fatalf("attendu 202, obtenu %d : %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := kv.ListByPrefix(db, "vm/vm-md4/metadata/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListByPrefix : %v", err)
|
||||||
|
}
|
||||||
|
if len(entries) != 0 {
|
||||||
|
t.Errorf("sans user_data, aucun document ne doit être stocké : %v", entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartVM_PasswordAndSSHKeyComeFromMetadata(t *testing.T) {
|
||||||
|
w, db := postVM(t, "vm-md5", VMMetadata{
|
||||||
|
Password: "$6$rounds$hash",
|
||||||
|
SSHKey: "ssh-ed25519 AAAA user@host",
|
||||||
|
})
|
||||||
|
if w.Code != http.StatusAccepted {
|
||||||
|
t.Fatalf("attendu 202, obtenu %d : %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, _ := kv.GetFromDB(db, "vm/vm-md5/password"); got != "$6$rounds$hash" {
|
||||||
|
t.Errorf("password attendu depuis metadata, obtenu %q", got)
|
||||||
|
}
|
||||||
|
if got, _ := kv.GetFromDB(db, "vm/vm-md5/sshkey"); got != "ssh-ed25519 AAAA user@host" {
|
||||||
|
t.Errorf("sshkey attendu depuis metadata, obtenu %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartVM_EmptyBase64MeansNoDocument(t *testing.T) {
|
||||||
|
w, db := postVM(t, "vm-md6", VMMetadata{UserData: base64.StdEncoding.EncodeToString([]byte(""))})
|
||||||
|
if w.Code != http.StatusAccepted {
|
||||||
|
t.Fatalf("attendu 202, obtenu %d : %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, _ := kv.ListByPrefix(db, "vm/vm-md6/metadata/")
|
||||||
|
if len(entries) != 0 {
|
||||||
|
t.Errorf("un base64 vide est indiscernable d'un champ absent : %v", entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
package agentapi
|
package agentapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
dispatcher "git.g3e.fr/syonad/two/internal/dispatcher/agent"
|
dispatcher "git.g3e.fr/syonad/two/internal/dispatcher/agent"
|
||||||
|
"git.g3e.fr/syonad/two/internal/metadata"
|
||||||
"git.g3e.fr/syonad/two/pkg/db/kv"
|
"git.g3e.fr/syonad/two/pkg/db/kv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -82,6 +85,13 @@ func (s *Server) startVM(w http.ResponseWriter, r *http.Request) {
|
||||||
disks[i] = dispatcher.VMDisk{Path: s.Path, Dev: s.Dev}
|
disks[i] = dispatcher.VMDisk{Path: s.Path, Dev: s.Dev}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
documents, err := decodeDocuments(req.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
json.NewEncoder(w).Encode(ErrorResponse{Error: err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cmd := dispatcher.StartVMCommand{
|
cmd := dispatcher.StartVMCommand{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Subnet: primary.Subnet,
|
Subnet: primary.Subnet,
|
||||||
|
|
@ -90,8 +100,9 @@ func (s *Server) startVM(w http.ResponseWriter, r *http.Request) {
|
||||||
Memory: req.Memory,
|
Memory: req.Memory,
|
||||||
CPUs: req.CPUs,
|
CPUs: req.CPUs,
|
||||||
UEFI: req.UEFI,
|
UEFI: req.UEFI,
|
||||||
Password: req.Password,
|
Password: req.Metadata.Password,
|
||||||
SSHKey: req.SSHKey,
|
SSHKey: req.Metadata.SSHKey,
|
||||||
|
Documents: documents,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.dispatcher.Prepare(cmd); err != nil {
|
if err := s.dispatcher.Prepare(cmd); err != nil {
|
||||||
|
|
@ -115,3 +126,14 @@ func (s *Server) startVM(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
json.NewEncoder(w).Encode(vm)
|
json.NewEncoder(w).Encode(vm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeDocuments(m VMMetadata) (map[string]string, error) {
|
||||||
|
if m.UserData == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
content, err := base64.StdEncoding.DecodeString(m.UserData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("metadata.user_data is not valid base64: %w", err)
|
||||||
|
}
|
||||||
|
return map[string]string{metadata.DocUserData: string(content)}, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue