mkvm/common.go

239 lines
6.3 KiB
Go
Raw Permalink Normal View History

package main
import (
"errors"
"fmt"
2024-12-09 06:59:06 +00:00
"io"
"math/rand"
"net"
2024-12-09 06:59:06 +00:00
"net/http"
"strings"
"github.com/sirupsen/logrus"
2024-12-09 06:59:06 +00:00
"github.com/spf13/cobra"
"libvirt.org/go/libvirt"
"libvirt.org/go/libvirtxml"
"mkvm/config"
"mkvm/libvirtx"
"mkvm/volumes/pools"
)
2024-12-09 06:59:06 +00:00
var (
argMemoryMB int
argCPUs int
argDiskSizeGB int
// cloudinit args
argSSHKeys []string
argSSHKeyURLs []string
argPackages []string
)
type domainModifier func(*libvirtxml.Domain) error
func getLibvirtAndPool() (*libvirt.Connect, pools.StoragePool, error) {
conn, err := libvirtx.New()
if err != nil {
return nil, nil, err
}
defer conn.Close()
pool, err := pools.GetPool(conn, config.C.StoragePool)
if err != nil {
return nil, nil, err
}
return conn, pool, nil
}
func getServerListenAddress(conn *libvirt.Connect) (string, error) {
serverInterfaceName := ""
if config.C.Network != "" {
nicSource.Network = &libvirtxml.DomainInterfaceSourceNetwork{Network: config.C.Network}
libvirtnet, err := conn.LookupNetworkByName(config.C.Network)
if err != nil {
logrus.WithField("network", config.C.Network).Error("error finding libvirt network")
return "", err
}
xmlstr, err := libvirtnet.GetXMLDesc(0)
if err != nil {
logrus.WithField("network", config.C.Network).Error("error getting network xml description")
return "", err
}
var net libvirtxml.Network
if err := net.Unmarshal(xmlstr); err != nil {
logrus.WithField("network", config.C.Network).Error("error parsing network xml description")
return "", err
}
serverInterfaceName = net.Bridge.Name
} else if config.C.Bridge != "" {
nicSource.Bridge = &libvirtxml.DomainInterfaceSourceBridge{Bridge: config.C.Bridge}
serverInterfaceName = config.C.Bridge
} else {
return "", errors.New("no network or bridge configured")
}
serverInterface, err := net.InterfaceByName(serverInterfaceName)
if err != nil {
logrus.Error("error finding local network interface to run server on")
return "", err
}
serverInterfaceAddrs, err := serverInterface.Addrs()
if err != nil {
logrus.Error("error finding local network interface's IP")
return "", err
}
if len(serverInterfaceAddrs) == 0 {
return "", fmt.Errorf("bridge interface %s does not have an IP on this machine", serverInterfaceName)
}
serverBindIP, _, err := net.ParseCIDR(serverInterfaceAddrs[0].String())
if err != nil {
logrus.WithField("interface", serverInterfaceName).WithField("address", serverInterfaceAddrs[0].String()).Error("error parsing local address")
return "", err
}
port := rand.Intn(65535-1025) + 1025
return fmt.Sprintf("%s:%d", serverBindIP, port), nil
}
func createDomain(conn *libvirt.Connect, pool pools.StoragePool, name string, modifiers ...domainModifier) error {
interfaces := []libvirtxml.DomainInterface{
{
Model: &libvirtxml.DomainInterfaceModel{Type: "virtio"},
Source: &nicSource,
},
}
// Create the domain
domainXML := &libvirtxml.Domain{
Type: "kvm",
Name: name,
Memory: &libvirtxml.DomainMemory{Value: uint(argMemoryMB), Unit: "MiB"},
VCPU: &libvirtxml.DomainVCPU{Value: uint(argCPUs)},
OS: &libvirtxml.DomainOS{
Type: &libvirtxml.DomainOSType{Arch: "x86_64", Type: "hvm"},
BootDevices: []libvirtxml.DomainBootDevice{{Dev: "hd"}},
},
Features: &libvirtxml.DomainFeatureList{
ACPI: &libvirtxml.DomainFeature{},
APIC: &libvirtxml.DomainFeatureAPIC{},
VMPort: &libvirtxml.DomainFeatureState{State: "off"},
},
CPU: &libvirtxml.DomainCPU{Mode: "host-model"},
Devices: &libvirtxml.DomainDeviceList{
Emulator: "/usr/bin/kvm",
Disks: []libvirtxml.DomainDisk{pool.GetDomainDiskXML(name)},
Channels: []libvirtxml.DomainChannel{
{
Source: &libvirtxml.DomainChardevSource{
UNIX: &libvirtxml.DomainChardevSourceUNIX{Path: "/var/lib/libvirt/qemu/f16x86_64.agent", Mode: "bind"},
},
Target: &libvirtxml.DomainChannelTarget{
VirtIO: &libvirtxml.DomainChannelTargetVirtIO{Name: "org.qemu.guest_agent.0"},
},
},
},
Consoles: []libvirtxml.DomainConsole{{Target: &libvirtxml.DomainConsoleTarget{}}},
Serials: []libvirtxml.DomainSerial{{}},
Interfaces: interfaces,
},
}
for _, modifier := range modifiers {
if err := modifier(domainXML); err != nil {
return err
}
}
domainXMLString, err := domainXML.Marshal()
if err != nil {
return err
}
logrus.Debug("defining domain from xml")
domain, err := conn.DomainDefineXML(domainXMLString)
if err != nil {
return fmt.Errorf("error defining domain from xml description: %v", err)
}
logrus.Debug("booting domain")
err = domain.Create()
if err != nil {
return fmt.Errorf("error creating domain: %v", err)
}
return nil
}
func setSMBIOS(smbios map[int]map[string]string) domainModifier {
return func(d *libvirtxml.Domain) error {
qemuArgs := []libvirtxml.DomainQEMUCommandlineArg{}
if d.QEMUCommandline != nil {
qemuArgs = d.QEMUCommandline.Args
}
for smbiosType, values := range smbios {
arg := libvirtxml.DomainQEMUCommandlineArg{
Value: fmt.Sprintf("type=%d", smbiosType),
}
for key, value := range values {
arg.Value = fmt.Sprintf("%s,%s=%s", arg.Value, key, value)
}
qemuArgs = append(qemuArgs, libvirtxml.DomainQEMUCommandlineArg{Value: "-smbios"}, arg)
}
d.QEMUCommandline = &libvirtxml.DomainQEMUCommandline{Args: qemuArgs}
return nil
}
}
2024-12-09 06:59:06 +00:00
func downloadSSHKeys(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
logrus.WithFields(logrus.Fields{
"url": url,
"status": resp.Status,
"body": string(body),
}).Error("non-200 response from SSH key URL")
return fmt.Errorf("non-200 response from SSH key URL: %s", resp.Status)
}
count := 0
for _, key := range strings.Split(string(body), "\n") {
key = strings.TrimSpace(key)
if key == "" {
continue
}
argSSHKeys = append(argSSHKeys, key)
count++
}
logrus.WithField("url", url).WithField("keys", count).Debug("downloaded SSH authorized keys")
return nil
}
func registerGlobalFlags(cmd cobra.Command) {
cmd.Flags().IntVarP(&argMemoryMB, "memory", "m", 1024, "amount of memory (in MB) to assign to the VM")
cmd.Flags().IntVarP(&argCPUs, "cpu", "c", 2, "the number of vCPU cores to assign to the VM")
cmd.Flags().IntVarP(&argDiskSizeGB, "disk", "d", 25, "disk size (in GB)")
}