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>
179 lines
4.8 KiB
Go
179 lines
4.8 KiB
Go
package vm
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
configuration "git.g3e.fr/syonad/two/internal/config/agent"
|
|
"git.g3e.fr/syonad/two/internal/dhcp"
|
|
"git.g3e.fr/syonad/two/internal/iptables"
|
|
"git.g3e.fr/syonad/two/internal/metadata"
|
|
"git.g3e.fr/syonad/two/internal/netif"
|
|
"git.g3e.fr/syonad/two/internal/netns"
|
|
"git.g3e.fr/syonad/two/internal/qemu"
|
|
"git.g3e.fr/syonad/two/internal/state"
|
|
|
|
"github.com/dgraph-io/badger/v4"
|
|
)
|
|
|
|
func StartVM(db *badger.DB, name string, cfg *configuration.Config) error {
|
|
current, err := state.Get(db, "vm/"+name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if current != state.Creating {
|
|
return nil
|
|
}
|
|
|
|
d, err := loadVM(db, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nic := d.primary()
|
|
|
|
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 {
|
|
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 := writeDHCPFiles(d, name); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := metadata.StartMetadata(metadata.NoCloudConfig{
|
|
Name: name,
|
|
VpcName: nic.vpcName,
|
|
BindIP: nic.interfaceIP,
|
|
BindPort: d.metadataPort,
|
|
Password: d.password,
|
|
SSHKEY: d.sshkey,
|
|
Documents: d.documents,
|
|
}, cfg, false); err != nil {
|
|
return fmt.Errorf("start metadata: %w", err)
|
|
}
|
|
|
|
qDisks := make([]qemu.DiskConfig, len(d.disks))
|
|
for i, disk := range d.disks {
|
|
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,
|
|
NICs: qNICs,
|
|
Disks: qDisks,
|
|
Memory: d.memory,
|
|
CPUs: d.cpus,
|
|
SerialDir: cfg.QEMU.SerialDir,
|
|
MonitorDir: cfg.QEMU.MonitorDir,
|
|
QMPDir: cfg.QEMU.QMPDir,
|
|
}
|
|
|
|
if d.uefi {
|
|
varsPath := filepath.Join(cfg.QEMU.UEFIVarsDir, name+"-uefi-vars.fd")
|
|
if err := copyFile(cfg.QEMU.OVMFVarsTemplate, varsPath); err != nil {
|
|
return fmt.Errorf("copy uefi vars: %w", err)
|
|
}
|
|
qcfg.UEFICodePath = cfg.QEMU.OVMFCodePath
|
|
qcfg.UEFIVarsPath = varsPath
|
|
}
|
|
|
|
if err := netns.Call(nic.vpcName, func() error {
|
|
return qemu.Start(qcfg)
|
|
}); err != nil {
|
|
return fmt.Errorf("start qemu: %w", err)
|
|
}
|
|
|
|
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
|
|
}
|
|
in, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer in.Close()
|
|
|
|
out, err := os.Create(dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer out.Close()
|
|
|
|
if _, err := io.Copy(out, in); err != nil {
|
|
return err
|
|
}
|
|
return out.Sync()
|
|
}
|