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
|
|
@ -19,10 +19,15 @@ type VMDisk struct {
|
|||
Dev string
|
||||
}
|
||||
|
||||
type VMNIC struct {
|
||||
Subnet string
|
||||
IP string
|
||||
Primary bool
|
||||
}
|
||||
|
||||
type StartVMCommand struct {
|
||||
Name string
|
||||
Subnet string
|
||||
IP string
|
||||
NICs []VMNIC
|
||||
Disks []VMDisk
|
||||
Memory int
|
||||
CPUs int
|
||||
|
|
@ -38,20 +43,28 @@ func (c StartVMCommand) Prepare(db *badger.DB, _ *configuration.Config) error {
|
|||
if _, err := kv.GetFromDB(db, "vm/"+c.Name+"/state"); err == nil {
|
||||
return fmt.Errorf("vm %q already exists", c.Name)
|
||||
}
|
||||
subnetState, err := state.Get(db, "subnet/"+c.Subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("subnet %q not found", c.Subnet)
|
||||
}
|
||||
if subnetState != state.Creating && subnetState != state.Running {
|
||||
return fmt.Errorf("subnet %q is %s", c.Subnet, subnetState)
|
||||
if err := c.validateNICs(db); err != nil {
|
||||
return err
|
||||
}
|
||||
port, err := allocateMetadataPort(db)
|
||||
if err != nil {
|
||||
return fmt.Errorf("allocate metadata port: %w", err)
|
||||
}
|
||||
state.Set(db, c.Key(), state.Creating)
|
||||
kv.AddInDB(db, "vm/"+c.Name+"/subnet", c.Subnet)
|
||||
kv.AddInDB(db, "vm/"+c.Name+"/ip", c.IP)
|
||||
for i, n := range c.NICs {
|
||||
prefix := fmt.Sprintf("vm/%s/nic/%d/", c.Name, i)
|
||||
if err := kv.AddInDB(db, prefix+"subnet", n.Subnet); err != nil {
|
||||
return fmt.Errorf("store nic %d subnet: %w", i, err)
|
||||
}
|
||||
if err := kv.AddInDB(db, prefix+"ip", n.IP); err != nil {
|
||||
return fmt.Errorf("store nic %d ip: %w", i, err)
|
||||
}
|
||||
if n.Primary {
|
||||
if err := kv.AddInDB(db, prefix+"primary", "true"); err != nil {
|
||||
return fmt.Errorf("store nic %d primary: %w", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
kv.AddInDB(db, "vm/"+c.Name+"/metadata_port", strconv.Itoa(port))
|
||||
for _, d := range c.Disks {
|
||||
kv.AddInDB(db, "vm/"+c.Name+"/disk/"+d.Dev, d.Path)
|
||||
|
|
@ -75,6 +88,31 @@ func (c StartVMCommand) Prepare(db *badger.DB, _ *configuration.Config) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// validateNICs vérifie qu'il y a exactement une interface primaire et que
|
||||
// chaque subnet référencé est utilisable.
|
||||
func (c StartVMCommand) validateNICs(db *badger.DB) error {
|
||||
if len(c.NICs) == 0 {
|
||||
return fmt.Errorf("vm %q has no interface", c.Name)
|
||||
}
|
||||
primaries := 0
|
||||
for _, n := range c.NICs {
|
||||
if n.Primary {
|
||||
primaries++
|
||||
}
|
||||
subnetState, err := state.Get(db, "subnet/"+n.Subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("subnet %q not found", n.Subnet)
|
||||
}
|
||||
if subnetState != state.Creating && subnetState != state.Running {
|
||||
return fmt.Errorf("subnet %q is %s", n.Subnet, subnetState)
|
||||
}
|
||||
}
|
||||
if primaries != 1 {
|
||||
return fmt.Errorf("vm %q has %d primary interfaces, expected exactly one", c.Name, primaries)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func allocateMetadataPort(db *badger.DB) (int, error) {
|
||||
entries, err := kv.ListByPrefix(db, "vm/")
|
||||
if err != nil {
|
||||
|
|
@ -99,21 +137,23 @@ func allocateMetadataPort(db *badger.DB) (int, error) {
|
|||
|
||||
func (c StartVMCommand) Execute(db *badger.DB, cfg *configuration.Config) error {
|
||||
timeout := time.After(time.Duration(cfg.Dispatcher.TimeoutSeconds) * time.Second)
|
||||
for {
|
||||
subnetState, err := state.Get(db, "subnet/"+c.Subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("subnet %q not found while waiting", c.Subnet)
|
||||
}
|
||||
if subnetState == state.Running {
|
||||
break
|
||||
}
|
||||
if subnetState != state.Creating {
|
||||
return fmt.Errorf("subnet %q is %s, cannot start vm %q", c.Subnet, subnetState, c.Name)
|
||||
}
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timed out waiting for subnet %q to be running", c.Subnet)
|
||||
case <-time.After(time.Duration(cfg.Dispatcher.PollSeconds) * time.Second):
|
||||
for _, n := range c.NICs {
|
||||
for {
|
||||
subnetState, err := state.Get(db, "subnet/"+n.Subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("subnet %q not found while waiting", n.Subnet)
|
||||
}
|
||||
if subnetState == state.Running {
|
||||
break
|
||||
}
|
||||
if subnetState != state.Creating {
|
||||
return fmt.Errorf("subnet %q is %s, cannot start vm %q", n.Subnet, subnetState, c.Name)
|
||||
}
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timed out waiting for subnet %q to be running", n.Subnet)
|
||||
case <-time.After(time.Duration(cfg.Dispatcher.PollSeconds) * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
return vm.StartVM(db, c.Name, cfg)
|
||||
|
|
|
|||
|
|
@ -15,10 +15,9 @@ func TestStartVMCommand_Prepare_SingleDisk(t *testing.T) {
|
|||
kv.AddInDB(db, "subnet/sn-1/vpc", "vpc-1")
|
||||
|
||||
cmd := StartVMCommand{
|
||||
Name: "vm-1",
|
||||
Subnet: "sn-1",
|
||||
IP: "10.0.0.5",
|
||||
Disks: []VMDisk{{Path: "/data/root.qcow2", Dev: "sda"}},
|
||||
Name: "vm-1",
|
||||
NICs: []VMNIC{{Subnet: "sn-1", IP: "10.0.0.5", Primary: true}},
|
||||
Disks: []VMDisk{{Path: "/data/root.qcow2", Dev: "sda"}},
|
||||
}
|
||||
if err := cmd.Prepare(db, nil); err != nil {
|
||||
t.Fatalf("Prepare a échoué : %v", err)
|
||||
|
|
@ -39,9 +38,8 @@ func TestStartVMCommand_Prepare_MultiDisk(t *testing.T) {
|
|||
kv.AddInDB(db, "subnet/sn-1/vpc", "vpc-1")
|
||||
|
||||
cmd := StartVMCommand{
|
||||
Name: "vm-2",
|
||||
Subnet: "sn-1",
|
||||
IP: "10.0.0.6",
|
||||
Name: "vm-2",
|
||||
NICs: []VMNIC{{Subnet: "sn-1", IP: "10.0.0.6", Primary: true}},
|
||||
Disks: []VMDisk{
|
||||
{Path: "/data/root.qcow2", Dev: "sda"},
|
||||
{Path: "/data/data.qcow2", Dev: "sdb"},
|
||||
|
|
@ -72,9 +70,8 @@ func TestStartVMCommand_Prepare_SlotGap(t *testing.T) {
|
|||
|
||||
// sdb absent au boot — slot réservé pour hotplug
|
||||
cmd := StartVMCommand{
|
||||
Name: "vm-3",
|
||||
Subnet: "sn-1",
|
||||
IP: "10.0.0.7",
|
||||
Name: "vm-3",
|
||||
NICs: []VMNIC{{Subnet: "sn-1", IP: "10.0.0.7", Primary: true}},
|
||||
Disks: []VMDisk{
|
||||
{Path: "/data/root.qcow2", Dev: "sda"},
|
||||
{Path: "/data/extra.qcow2", Dev: "sdc"},
|
||||
|
|
@ -101,10 +98,9 @@ func TestStartVMCommand_Prepare_NoVolumePath(t *testing.T) {
|
|||
kv.AddInDB(db, "subnet/sn-1/vpc", "vpc-1")
|
||||
|
||||
cmd := StartVMCommand{
|
||||
Name: "vm-4",
|
||||
Subnet: "sn-1",
|
||||
IP: "10.0.0.8",
|
||||
Disks: []VMDisk{{Path: "/data/root.qcow2", Dev: "sda"}},
|
||||
Name: "vm-4",
|
||||
NICs: []VMNIC{{Subnet: "sn-1", IP: "10.0.0.8", Primary: true}},
|
||||
Disks: []VMDisk{{Path: "/data/root.qcow2", Dev: "sda"}},
|
||||
}
|
||||
if err := cmd.Prepare(db, nil); err != nil {
|
||||
t.Fatalf("Prepare a échoué : %v", err)
|
||||
|
|
@ -165,10 +161,9 @@ func TestStartVMCommand_Prepare_Duplicate(t *testing.T) {
|
|||
kv.AddInDB(db, "vm/vm-exist/state", "running")
|
||||
|
||||
cmd := StartVMCommand{
|
||||
Name: "vm-exist",
|
||||
Subnet: "sn-1",
|
||||
IP: "10.0.0.9",
|
||||
Disks: []VMDisk{{Path: "/data/root.qcow2", Dev: "sda"}},
|
||||
Name: "vm-exist",
|
||||
NICs: []VMNIC{{Subnet: "sn-1", IP: "10.0.0.9", Primary: true}},
|
||||
Disks: []VMDisk{{Path: "/data/root.qcow2", Dev: "sda"}},
|
||||
}
|
||||
if err := cmd.Prepare(db, nil); err == nil {
|
||||
t.Error("Prepare devrait échouer si la VM existe déjà")
|
||||
|
|
@ -185,8 +180,7 @@ func prepareWithDocuments(t *testing.T, docs map[string]string) *badger.DB {
|
|||
|
||||
cmd := StartVMCommand{
|
||||
Name: "vm-doc",
|
||||
Subnet: "sn-1",
|
||||
IP: "10.0.0.5",
|
||||
NICs: []VMNIC{{Subnet: "sn-1", IP: "10.0.0.5", Primary: true}},
|
||||
Disks: []VMDisk{{Path: "/data/root.qcow2", Dev: "vda"}},
|
||||
Documents: docs,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue