f-33: net: add multi net in dispatch et vms #33
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
1ec1d44c1a
commit
0237acba81
15 changed files with 753 additions and 146 deletions
|
|
@ -3,6 +3,7 @@ package agentapi
|
|||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
|
@ -71,6 +72,52 @@ func (s *Server) stopVM(w http.ResponseWriter, _ *http.Request, name string) {
|
|||
json.NewEncoder(w).Encode(vm)
|
||||
}
|
||||
|
||||
// interfacesFromDB reconstruit les interfaces depuis vm/<name>/nic/<index>/…,
|
||||
// triées par index — celui-ci détermine le slot PCI, donc le nom de l'interface
|
||||
// dans le guest.
|
||||
func interfacesFromDB(prefix string, entries map[string]string) []VMInterface {
|
||||
nicPrefix := prefix + "nic/"
|
||||
byIndex := make(map[int]*VMInterface)
|
||||
|
||||
for key, value := range entries {
|
||||
rest := strings.TrimPrefix(key, nicPrefix)
|
||||
if rest == key {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(rest, "/")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
idx, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if byIndex[idx] == nil {
|
||||
byIndex[idx] = &VMInterface{}
|
||||
}
|
||||
switch parts[1] {
|
||||
case "subnet":
|
||||
byIndex[idx].Subnet = value
|
||||
case "ip":
|
||||
byIndex[idx].IP = value
|
||||
case "primary":
|
||||
byIndex[idx].Primary = value == "true"
|
||||
}
|
||||
}
|
||||
|
||||
indexes := make([]int, 0, len(byIndex))
|
||||
for idx := range byIndex {
|
||||
indexes = append(indexes, idx)
|
||||
}
|
||||
sort.Ints(indexes)
|
||||
|
||||
ifaces := make([]VMInterface, 0, len(indexes))
|
||||
for _, idx := range indexes {
|
||||
ifaces = append(ifaces, *byIndex[idx])
|
||||
}
|
||||
return ifaces
|
||||
}
|
||||
|
||||
func vmFromDB(name string, entries map[string]string) (VM, error) {
|
||||
prefix := "vm/" + name + "/"
|
||||
vm := VM{Name: name}
|
||||
|
|
@ -81,11 +128,7 @@ func vmFromDB(name string, entries map[string]string) (VM, error) {
|
|||
vm.CPUs, _ = strconv.Atoi(entries[prefix+"cpus"])
|
||||
vm.UEFI = entries[prefix+"uefi"] == "true"
|
||||
|
||||
subnet := entries[prefix+"subnet"]
|
||||
ip := entries[prefix+"ip"]
|
||||
if subnet != "" || ip != "" {
|
||||
vm.Interfaces = []VMInterface{{Subnet: subnet, IP: ip, Primary: true}}
|
||||
}
|
||||
vm.Interfaces = interfacesFromDB(prefix, entries)
|
||||
|
||||
diskPrefix := prefix + "disk/"
|
||||
for key, path := range entries {
|
||||
|
|
|
|||
|
|
@ -262,3 +262,89 @@ func TestStartVM_EmptyBase64MeansNoDocument(t *testing.T) {
|
|||
t.Errorf("un base64 vide est indiscernable d'un champ absent : %v", entries)
|
||||
}
|
||||
}
|
||||
|
||||
// --- interfaces multiples ---
|
||||
|
||||
func TestVmFromDB_MultipleInterfacesSortedByIndex(t *testing.T) {
|
||||
vm, err := vmFromDB("vm-multi", map[string]string{
|
||||
"vm/vm-multi/state": "running",
|
||||
"vm/vm-multi/nic/1/subnet": "sn-2",
|
||||
"vm/vm-multi/nic/1/ip": "10.2.0.5",
|
||||
"vm/vm-multi/nic/0/subnet": "sn-1",
|
||||
"vm/vm-multi/nic/0/ip": "10.1.0.5",
|
||||
"vm/vm-multi/nic/0/primary": "true",
|
||||
"vm/vm-multi/disk/vda": "/data/root.qcow2",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("vmFromDB : %v", err)
|
||||
}
|
||||
if len(vm.Interfaces) != 2 {
|
||||
t.Fatalf("2 interfaces attendues, obtenu %d : %+v", len(vm.Interfaces), vm.Interfaces)
|
||||
}
|
||||
if vm.Interfaces[0].Subnet != "sn-1" || !vm.Interfaces[0].Primary {
|
||||
t.Errorf("la première doit être l'index 0, primaire : %+v", vm.Interfaces[0])
|
||||
}
|
||||
if vm.Interfaces[1].Subnet != "sn-2" || vm.Interfaces[1].Primary {
|
||||
t.Errorf("la seconde doit être l'index 1, non primaire : %+v", vm.Interfaces[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartVM_StoresAllInterfaces(t *testing.T) {
|
||||
s, db := newTestServer(t)
|
||||
for _, sn := range []string{"sn-1", "sn-2"} {
|
||||
kv.AddInDB(db, "subnet/"+sn+"/state", "running")
|
||||
kv.AddInDB(db, "subnet/"+sn+"/vpc", "vpc-1")
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(VMCreateRequest{
|
||||
Name: "vm-multi",
|
||||
Interfaces: []VMInterface{
|
||||
{Subnet: "sn-1", IP: "10.1.0.5", Primary: true},
|
||||
{Subnet: "sn-2", IP: "10.2.0.5"},
|
||||
},
|
||||
Storage: []VMStorage{{Path: "/data/root.qcow2", Dev: "vda"}},
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
s.VmsHandler(w, httptest.NewRequest(http.MethodPost, "/vms", bytes.NewReader(body)))
|
||||
if w.Code != http.StatusAccepted {
|
||||
t.Fatalf("attendu 202, obtenu %d : %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
if got, _ := kv.GetFromDB(db, "vm/vm-multi/nic/1/subnet"); got != "sn-2" {
|
||||
t.Errorf("seconde interface non stockée : %q", got)
|
||||
}
|
||||
if got, _ := kv.GetFromDB(db, "vm/vm-multi/nic/0/primary"); got != "true" {
|
||||
t.Errorf("primaire non marquée : %q", got)
|
||||
}
|
||||
if _, err := kv.GetFromDB(db, "vm/vm-multi/nic/1/primary"); err == nil {
|
||||
t.Error("une interface non primaire ne doit pas porter la clé primary")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartVM_RejectsZeroOrTwoPrimaries(t *testing.T) {
|
||||
cases := map[string][]VMInterface{
|
||||
"aucune primaire": {{Subnet: "sn-1", IP: "10.1.0.5"}},
|
||||
"deux primaires": {
|
||||
{Subnet: "sn-1", IP: "10.1.0.5", Primary: true},
|
||||
{Subnet: "sn-2", IP: "10.2.0.5", Primary: true},
|
||||
},
|
||||
}
|
||||
for label, ifaces := range cases {
|
||||
s, db := newTestServer(t)
|
||||
for _, sn := range []string{"sn-1", "sn-2"} {
|
||||
kv.AddInDB(db, "subnet/"+sn+"/state", "running")
|
||||
kv.AddInDB(db, "subnet/"+sn+"/vpc", "vpc-1")
|
||||
}
|
||||
body, _ := json.Marshal(VMCreateRequest{
|
||||
Name: "vm-bad",
|
||||
Interfaces: ifaces,
|
||||
Storage: []VMStorage{{Path: "/data/root.qcow2", Dev: "vda"}},
|
||||
})
|
||||
w := httptest.NewRecorder()
|
||||
s.VmsHandler(w, httptest.NewRequest(http.MethodPost, "/vms", bytes.NewReader(body)))
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("%s : attendu 400, obtenu %d — %s", label, w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,16 +67,17 @@ func (s *Server) startVM(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
var primary *VMInterface
|
||||
for i := range req.Interfaces {
|
||||
if req.Interfaces[i].Primary {
|
||||
primary = &req.Interfaces[i]
|
||||
break
|
||||
nics := make([]dispatcher.VMNIC, len(req.Interfaces))
|
||||
primaries := 0
|
||||
for i, iface := range req.Interfaces {
|
||||
nics[i] = dispatcher.VMNIC{Subnet: iface.Subnet, IP: iface.IP, Primary: iface.Primary}
|
||||
if iface.Primary {
|
||||
primaries++
|
||||
}
|
||||
}
|
||||
if primary == nil {
|
||||
if primaries != 1 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Error: "one interface must be primary"})
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Error: "exactly one interface must be primary"})
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +95,7 @@ func (s *Server) startVM(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
cmd := dispatcher.StartVMCommand{
|
||||
Name: req.Name,
|
||||
Subnet: primary.Subnet,
|
||||
IP: primary.IP,
|
||||
NICs: nics,
|
||||
Disks: disks,
|
||||
Memory: req.Memory,
|
||||
CPUs: req.CPUs,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue