f-33: vm: start vms with multiple nic
All checks were successful
Release Pipeline / checksums (push) Successful in 4s
Release Pipeline / release (push) Successful in 11s
Release Pipeline / set-release-target (push) Successful in 2s
Release Pipeline / upload-assets (agent.service, systemd/agent.service) (push) Successful in 4s
Release Pipeline / upload-assets (dnsmasq@.service, systemd/dnsmasq@.service) (push) Successful in 4s
Release Pipeline / upload-assets (metadata@.service, systemd/metadata@.service) (push) Successful in 4s
Release Pipeline / upload-assets (run-dnsmasq-in-netns.sh, scripts/run-dnsmasq-in-netns.sh) (push) Successful in 4s
Release Pipeline / build (agent, amd64, linux) (push) Successful in 0s
Release Pipeline / build (metadata, amd64, linux) (push) Successful in 0s
Release Pipeline / publish (push) Successful in 0s
Release Pipeline / build (push) Successful in 1m36s
All checks were successful
Release Pipeline / checksums (push) Successful in 4s
Release Pipeline / release (push) Successful in 11s
Release Pipeline / set-release-target (push) Successful in 2s
Release Pipeline / upload-assets (agent.service, systemd/agent.service) (push) Successful in 4s
Release Pipeline / upload-assets (dnsmasq@.service, systemd/dnsmasq@.service) (push) Successful in 4s
Release Pipeline / upload-assets (metadata@.service, systemd/metadata@.service) (push) Successful in 4s
Release Pipeline / upload-assets (run-dnsmasq-in-netns.sh, scripts/run-dnsmasq-in-netns.sh) (push) Successful in 4s
Release Pipeline / build (agent, amd64, linux) (push) Successful in 0s
Release Pipeline / build (metadata, amd64, linux) (push) Successful in 0s
Release Pipeline / publish (push) Successful in 0s
Release Pipeline / build (push) Successful in 1m36s
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
0237acba81
commit
26d5805747
8 changed files with 309 additions and 25 deletions
|
|
@ -3,6 +3,7 @@ package vm
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
|
|
@ -33,19 +34,28 @@ func StartVM(db *badger.DB, name string, cfg *configuration.Config) error {
|
|||
}
|
||||
nic := d.primary()
|
||||
|
||||
if err := netif.CreateTap(nic.tapID, nic.bridge, nic.vpcName); err != nil {
|
||||
return fmt.Errorf("create tap: %w", err)
|
||||
for _, n := range d.nics {
|
||||
if err := netif.CreateTap(n.tapID, n.bridge, n.vpcName); err != nil {
|
||||
return fmt.Errorf("create tap of interface %d: %w", n.index, err)
|
||||
}
|
||||
}
|
||||
|
||||
// La redirection est posée pour chaque IP de la VM vers le serveur de
|
||||
// métadonnées de l'interface primaire. Toutes les interfaces étant dans le
|
||||
// même VPC, donc le même netns, il est joignable depuis n'importe laquelle.
|
||||
if err := netns.Call(nic.vpcName, func() error {
|
||||
return iptables.AddMetadataRedirect(nic.ip, nic.interfaceIP, d.metadataPort)
|
||||
for _, n := range d.nics {
|
||||
if err := iptables.AddMetadataRedirect(n.ip, nic.interfaceIP, d.metadataPort); err != nil {
|
||||
return fmt.Errorf("interface %d: %w", n.index, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("add metadata redirect: %w", err)
|
||||
}
|
||||
|
||||
if err := dhcp.WriteReservations(dhcp.DefaultConfDir, nic.vpcName+"_"+nic.bridge, name,
|
||||
[]dhcp.Reservation{{MAC: nic.mac, IP: nic.ip}}); err != nil {
|
||||
return fmt.Errorf("write dhcp reservation: %w", err)
|
||||
if err := writeDHCPFiles(d, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := metadata.StartMetadata(metadata.NoCloudConfig{
|
||||
|
|
@ -65,10 +75,14 @@ func StartVM(db *badger.DB, name string, cfg *configuration.Config) error {
|
|||
qDisks[i] = qemu.DiskConfig{Path: disk.path, Dev: disk.dev}
|
||||
}
|
||||
|
||||
qNICs := make([]qemu.NICConfig, len(d.nics))
|
||||
for i, n := range d.nics {
|
||||
qNICs[i] = qemu.NICConfig{TapID: n.tapID, Mac: n.mac}
|
||||
}
|
||||
|
||||
qcfg := qemu.Config{
|
||||
Name: name,
|
||||
TapID: nic.tapID,
|
||||
Mac: nic.mac,
|
||||
NICs: qNICs,
|
||||
Disks: qDisks,
|
||||
Memory: d.memory,
|
||||
CPUs: d.cpus,
|
||||
|
|
@ -95,6 +109,53 @@ func StartVM(db *badger.DB, name string, cfg *configuration.Config) error {
|
|||
return state.Set(db, "vm/"+name, state.Running)
|
||||
}
|
||||
|
||||
// writeDHCPFiles écrit, pour chaque subnet touché par la VM, les réservations
|
||||
// de ses interfaces et les options qui suppriment la route par défaut sur les
|
||||
// interfaces non primaires. Le subnet de l'interface primaire ne reçoit aucune
|
||||
// option : les options non taggées du subnet portent déjà la route par défaut.
|
||||
func writeDHCPFiles(d vmData, name string) error {
|
||||
type subnetFiles struct {
|
||||
nic nicData
|
||||
reservations []dhcp.Reservation
|
||||
tags []string
|
||||
}
|
||||
bySubnet := make(map[string]*subnetFiles)
|
||||
|
||||
for _, n := range d.nics {
|
||||
confName := n.vpcName + "_" + n.bridge
|
||||
if bySubnet[confName] == nil {
|
||||
bySubnet[confName] = &subnetFiles{nic: n}
|
||||
}
|
||||
f := bySubnet[confName]
|
||||
f.reservations = append(f.reservations, dhcp.Reservation{
|
||||
MAC: n.mac, IP: n.ip, Tag: nicTag(name, n.index),
|
||||
})
|
||||
if !n.primary {
|
||||
f.tags = append(f.tags, nicTag(name, n.index))
|
||||
}
|
||||
}
|
||||
|
||||
for confName, f := range bySubnet {
|
||||
if err := dhcp.WriteReservations(dhcp.DefaultConfDir, confName, name, f.reservations); err != nil {
|
||||
return fmt.Errorf("write dhcp reservations on %s: %w", confName, err)
|
||||
}
|
||||
if err := dhcp.WriteVMOptions(dhcp.DefaultConfDir, confName, name, f.tags, dhcp.Config{
|
||||
InterfaceIP: net.ParseIP(f.nic.interfaceIP),
|
||||
VPCRoute: f.nic.vpcCIDR,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("write dhcp options on %s: %w", confName, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nicTag identifie une interface auprès de dnsmasq. Il est par interface et non
|
||||
// par VM : deux interfaces d'une même VM peuvent partager un subnet, et n'y
|
||||
// avoir pas le même rôle.
|
||||
func nicTag(vmName string, index int) string {
|
||||
return fmt.Sprintf("%s-%d", vmName, index)
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package vm
|
|||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -27,6 +28,8 @@ type nicData struct {
|
|||
mac string
|
||||
tapID int
|
||||
primary bool
|
||||
mode string
|
||||
vpcCIDR *net.IPNet
|
||||
}
|
||||
|
||||
type vmData struct {
|
||||
|
|
@ -190,6 +193,23 @@ func loadNIC(db *badger.DB, name string, idx int, entries map[string]string) (ni
|
|||
}
|
||||
n.interfaceIP = interfaceIP
|
||||
|
||||
n.mode, err = kv.GetFromDB(db, "subnet/"+n.subnetName+"/mode")
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("get mode of subnet %s: %w", n.subnetName, err)
|
||||
}
|
||||
|
||||
if n.mode != "bridge" {
|
||||
cidrStr, err := kv.GetFromDB(db, "vpc/"+n.vpcName+"/cidr")
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("get cidr of vpc %s: %w", n.vpcName, err)
|
||||
}
|
||||
_, vpcCIDR, err := net.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("parse cidr of vpc %s: %w", n.vpcName, err)
|
||||
}
|
||||
n.vpcCIDR = vpcCIDR
|
||||
}
|
||||
|
||||
n.ip = entries[prefix+"ip"]
|
||||
if n.ip == "" {
|
||||
return n, fmt.Errorf("nic %d of vm %s has no ip", idx, name)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ func newVMInDB(t *testing.T) *badger.DB {
|
|||
kv.AddInDB(db, "vm/vm-1/nic/0/primary", "true")
|
||||
kv.AddInDB(db, "subnet/sn-000001/vpc", "vp-admin")
|
||||
kv.AddInDB(db, "subnet/sn-000001/interface_ip", "10.1.1.1")
|
||||
kv.AddInDB(db, "subnet/sn-000001/mode", "vxlan")
|
||||
kv.AddInDB(db, "vpc/vp-admin/cidr", "192.168.0.0/16")
|
||||
kv.AddInDB(db, "subnet/sn-000001/dhcp/10.1.1.2", "00:22:33:00:01:02")
|
||||
kv.AddInDB(db, "vm/vm-1/nic/0/ip", "10.1.1.2")
|
||||
kv.AddInDB(db, "vm/vm-1/metadata_port", "8081")
|
||||
|
|
@ -104,6 +106,8 @@ func addNIC(t *testing.T, db *badger.DB, idx int, subnet, ip, mac string, primar
|
|||
}
|
||||
kv.AddInDB(db, "subnet/"+subnet+"/vpc", "vp-admin")
|
||||
kv.AddInDB(db, "subnet/"+subnet+"/interface_ip", "10.1.1.1")
|
||||
kv.AddInDB(db, "subnet/"+subnet+"/mode", "vxlan")
|
||||
kv.AddInDB(db, "vpc/vp-admin/cidr", "192.168.0.0/16")
|
||||
kv.AddInDB(db, "subnet/"+subnet+"/dhcp/"+ip, mac)
|
||||
}
|
||||
|
||||
|
|
@ -170,6 +174,7 @@ func TestLoadVM_NoPrimaryIsAnError(t *testing.T) {
|
|||
kv.AddInDB(db, "vm/vm-1/memory", "2048")
|
||||
kv.AddInDB(db, "vm/vm-1/cpus", "2")
|
||||
addNIC(t, db, 0, "sn-000001", "10.1.1.2", "00:22:33:00:01:02", false)
|
||||
kv.AddInDB(db, "subnet/sn-000001/mode", "vxlan")
|
||||
|
||||
if _, err := loadVM(db, "vm-1"); err == nil {
|
||||
t.Error("aucune interface primaire : loadVM doit échouer plutôt que de laisser StartVM choisir au hasard")
|
||||
|
|
|
|||
|
|
@ -49,7 +49,12 @@ func StopVM(db *badger.DB, name string, cfg *configuration.Config) error {
|
|||
// socket absent ou QEMU déjà arrêté : cleanup direct
|
||||
|
||||
if err := netns.Call(nic.vpcName, func() error {
|
||||
return iptables.DeleteMetadataRedirect(nic.ip, nic.interfaceIP, d.metadataPort)
|
||||
for _, n := range d.nics {
|
||||
if err := iptables.DeleteMetadataRedirect(n.ip, nic.interfaceIP, d.metadataPort); err != nil {
|
||||
return fmt.Errorf("interface %d: %w", n.index, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("delete metadata redirect: %w", err)
|
||||
}
|
||||
|
|
@ -58,11 +63,13 @@ func StopVM(db *badger.DB, name string, cfg *configuration.Config) error {
|
|||
return fmt.Errorf("stop metadata: %w", err)
|
||||
}
|
||||
|
||||
if err := netif.DeleteTap(nic.tapID, nic.vpcName); err != nil {
|
||||
return fmt.Errorf("delete tap: %w", err)
|
||||
for _, n := range d.nics {
|
||||
if err := netif.DeleteTap(n.tapID, n.vpcName); err != nil {
|
||||
return fmt.Errorf("delete tap of interface %d: %w", n.index, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := removeDHCPReservation(nic, name); err != nil {
|
||||
if err := removeDHCPFiles(d, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -74,12 +81,26 @@ func StopVM(db *badger.DB, name string, cfg *configuration.Config) error {
|
|||
return state.Set(db, "vm/"+name, state.Deleted)
|
||||
}
|
||||
|
||||
// removeDHCPReservation retire le fichier de réservation puis redémarre dnsmasq :
|
||||
// un fichier ajouté dans un dhcp-hostsdir est relu à chaud, un fichier retiré ne
|
||||
// l'est pas (vérifié sur dnsmasq 2.90).
|
||||
func removeDHCPReservation(nic nicData, name string) error {
|
||||
confName := nic.vpcName + "_" + nic.bridge
|
||||
// removeDHCPFiles retire les fichiers de la VM dans chaque subnet qu'elle
|
||||
// touche, puis redémarre les dnsmasq concernés : un fichier ajouté dans un
|
||||
// dhcp-hostsdir est relu à chaud, un fichier retiré ne l'est pas (vérifié sur
|
||||
// dnsmasq 2.90).
|
||||
func removeDHCPFiles(d vmData, name string) error {
|
||||
seen := make(map[string]bool)
|
||||
for _, n := range d.nics {
|
||||
confName := n.vpcName + "_" + n.bridge
|
||||
if seen[confName] {
|
||||
continue
|
||||
}
|
||||
seen[confName] = true
|
||||
if err := removeDHCPReservation(confName, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeDHCPReservation(confName, name string) error {
|
||||
if err := dhcp.RemoveReservations(dhcp.DefaultConfDir, confName, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue