From 524c039dfefacb2b3c724cd6623beebd85b01c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Ma=C5=82ota-W=C3=B3jcik?= <59281144+outofforest@users.noreply.github.com> Date: Sun, 25 Jun 2023 20:53:17 +0200 Subject: [PATCH] Add filesystems to VMs automatically (#226) --- commands/start.go | 1 + config/start.go | 7 +++ functions.go | 2 +- vm.go | 107 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 114 insertions(+), 3 deletions(-) diff --git a/commands/start.go b/commands/start.go index a044e65..301afc3 100644 --- a/commands/start.go +++ b/commands/start.go @@ -43,6 +43,7 @@ func NewStartCommand(cmdF *CmdFactory) *cobra.Command { formatF = cmdF.AddFormatFlags(cmd) cmd.Flags().StringVar(&startF.LibvirtAddr, "libvirt-addr", "unix:///var/run/libvirt/libvirt-sock", "Address libvirt listens on") cmd.Flags().StringVar(&startF.XMLDir, "xml-dir", must.String(os.UserHomeDir())+"/osman", "Directory where VM definition is taken from if vm-file argument is not provided") + cmd.Flags().StringVar(&startF.VolumeDir, "volume-dir", "/tank/vms", "Directory where vm-specific folder exists containing subfolders to be mounted as filesystems in the VM") cmd.Flags().StringVar(&startF.VMFile, "vm", "", "Defines VM for mounted image in Libvirt using provided file. If flag is provided without value or value is `auto` then file is derived as /.xml") cmd.Flags().Lookup("vm").NoOptDefVal = "auto" return cmd diff --git a/config/start.go b/config/start.go index ca31b76..41a6ed5 100644 --- a/config/start.go +++ b/config/start.go @@ -12,6 +12,9 @@ type StartFactory struct { // XMLDir is a directory where VM definition is taken from if xml file is not provided explicitly XMLDir string + // VolumeDir is a directory where vm-specific folder exists containing subfolders to be mounted as filesystems in the VM + VolumeDir string + // VMFile is the file containing VM definition VMFile string @@ -23,6 +26,7 @@ type StartFactory struct { func (f *StartFactory) Config(args Args) Start { config := Start{ XMLDir: f.XMLDir, + VolumeDir: f.VolumeDir, LibvirtAddr: f.LibvirtAddr, } if f.VMFile != "auto" { @@ -66,6 +70,9 @@ type Start struct { // XMLDir is a directory where VM definition is taken from if xml file is not provided explicitly XMLDir string + // VolumeDir is a directory where vm-specific folder exists containing subfolders to be mounted as filesystems in the VM + VolumeDir string + // VMFile is the file containing VM definition VMFile string diff --git a/functions.go b/functions.go index f8f2d66..4e85dd7 100644 --- a/functions.go +++ b/functions.go @@ -142,7 +142,7 @@ func Start(ctx context.Context, storage config.Storage, start config.Start, s st return types.BuildInfo{}, err } - if err := deployVM(ctx, l, domain, info, start.MountKey); err != nil { + if err := deployVM(ctx, l, domain, info, start); err != nil { return types.BuildInfo{}, err } return info, nil diff --git a/vm.go b/vm.go index 6e2aa2f..8fd5341 100644 --- a/vm.go +++ b/vm.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "net" + "os" + "path/filepath" "sort" "strconv" "strings" @@ -23,6 +25,7 @@ import ( "golang.org/x/sys/unix" "libvirt.org/go/libvirtxml" + "github.com/outofforest/osman/config" "github.com/outofforest/osman/infra/types" ) @@ -894,8 +897,101 @@ func prepareMetadata(domainDoc libvirtxml.Domain, info types.BuildInfo) (*libvir return domainDoc.Metadata, meta, nil } -func deployVM(ctx context.Context, l *libvirt.Libvirt, domainDoc libvirtxml.Domain, info types.BuildInfo, buildKey types.BuildKey) error { - domainDoc.Name = buildKey.String() +func prepareFilesystems(l *libvirt.Libvirt, domainDoc libvirtxml.Domain, dir string) ([]libvirtxml.DomainFilesystem, error) { + items, err := os.ReadDir(dir) + switch { + case err == nil: + case errors.Is(err, os.ErrNotExist): + return nil, nil + default: + return nil, errors.WithStack(err) + } + + if len(items) == 0 { + return nil, nil + } + + dirs := make(map[string]string, len(items)) + for _, i := range items { + if !i.IsDir() { + continue + } + dirs[filepath.Join(dir, i.Name())] = i.Name() + } + + if len(dirs) == 0 { + return nil, nil + } + + domains, _, err := l.ConnectListAllDomains(1, libvirt.ConnectListDomainsActive|libvirt.ConnectListDomainsInactive) + if err != nil { + return nil, errors.WithStack(err) + } + domainDocs := make([]libvirtxml.Domain, 0, len(domains)) + domainDocs = append(domainDocs, domainDoc) + + for _, d := range domains { + xml, err := l.DomainGetXMLDesc(d, 0) + if err != nil { + return nil, errors.WithStack(err) + } + var domainDoc libvirtxml.Domain + if err := domainDoc.Unmarshal(xml); err != nil { + return nil, errors.WithStack(err) + } + + if domainDoc.Devices == nil || len(domainDoc.Devices.Filesystems) == 0 { + continue + } + + domainDocs = append(domainDocs, domainDoc) + } + + for _, domainDoc := range domainDocs { + for _, fs := range domainDoc.Devices.Filesystems { + if fs.Source == nil || fs.Source.Mount == nil { + continue + } + if _, exists := dirs[fs.Source.Mount.Dir]; exists { + return nil, errors.Errorf("directory %s is already mounted in VM %s", fs.Source.Mount.Dir, domainDoc.Name) + } + } + } + + filesystems := make([]libvirtxml.DomainFilesystem, 0, len(dirs)) + for dir, name := range dirs { + filesystems = append(filesystems, libvirtxml.DomainFilesystem{ + Driver: &libvirtxml.DomainFilesystemDriver{ + Type: "virtiofs", + }, + Binary: &libvirtxml.DomainFilesystemBinary{ + Path: "/usr/libexec/virtiofsd", + ThreadPool: &libvirtxml.DomainFilesystemBinaryThreadPool{ + Size: 1, + }, + }, + Source: &libvirtxml.DomainFilesystemSource{ + Mount: &libvirtxml.DomainFilesystemSourceMount{ + Dir: dir, + }, + }, + Target: &libvirtxml.DomainFilesystemTarget{ + Dir: name, + }, + }) + } + + return filesystems, nil +} + +func deployVM( + ctx context.Context, + l *libvirt.Libvirt, + domainDoc libvirtxml.Domain, + info types.BuildInfo, + start config.Start, +) error { + domainDoc.Name = start.MountKey.String() uuid, err := uuid.NewUUID() if err != nil { @@ -966,6 +1062,13 @@ func deployVM(ctx context.Context, l *libvirt.Libvirt, domainDoc libvirtxml.Doma }, }) + filesystems, err := prepareFilesystems(l, domainDoc, filepath.Join(start.VolumeDir, start.MountKey.Name)) + if err != nil { + return err + } + + domainDoc.Devices.Filesystems = append(domainDoc.Devices.Filesystems, filesystems...) + if domainDoc.OS == nil { domainDoc.OS = &libvirtxml.DomainOS{} }