From 8e7eeaa4dd14621bda15e396fcd7b9187bc500c5 Mon Sep 17 00:00:00 2001 From: Anders F Björklund Date: Sun, 5 Sep 2021 22:16:59 +0200 Subject: Implement virtfs volumes for podman machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow using the built-in 9pfs feature of qemu, mounting host directories into vm mountpoints. The volumes are generic, the mounts are specific. Wait for the machine to be "running", otherwise the SSH function might throw an error instead. Increase the default msize from 8 KiB to 128 KiB [NO NEW TESTS NEEDED] Signed-off-by: Anders F Björklund --- cmd/podman/machine/init.go | 4 +++ docs/source/markdown/podman-machine-init.1.md | 11 ++++++ pkg/machine/config.go | 1 + pkg/machine/qemu/config.go | 9 +++++ pkg/machine/qemu/machine.go | 48 ++++++++++++++++++++++++++- 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index 14e87c201..b913a252e 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -88,6 +88,10 @@ func init() { flags.StringVar(&initOpts.ImagePath, ImagePathFlagName, cfg.Machine.Image, "Path to qcow image") _ = initCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault) + VolumeFlagName := "volume" + flags.StringArrayVarP(&initOpts.Volumes, VolumeFlagName, "v", []string{}, "Volumes to mount, source:target") + _ = initCmd.RegisterFlagCompletionFunc(VolumeFlagName, completion.AutocompleteDefault) + IgnitionPathFlagName := "ignition-path" flags.StringVar(&initOpts.IgnitionPath, IgnitionPathFlagName, "", "Path to ignition file") _ = initCmd.RegisterFlagCompletionFunc(IgnitionPathFlagName, completion.AutocompleteDefault) diff --git a/docs/source/markdown/podman-machine-init.1.md b/docs/source/markdown/podman-machine-init.1.md index aead6c695..b936447fb 100644 --- a/docs/source/markdown/podman-machine-init.1.md +++ b/docs/source/markdown/podman-machine-init.1.md @@ -61,6 +61,16 @@ Set the timezone for the machine and containers. Valid values are `local` or a `timezone` such as `America/Chicago`. A value of `local`, which is the default, means to use the timezone of the machine host. +#### **--volume**, **-v**=*source:target* + +Mounts a volume from source to target. + +Create a mount. If /host-dir:/machine-dir is specified as the `*source:target*`, +Podman mounts _host-dir_ in the host to _machine-dir_ in the Podman machine. + +The root filesystem is mounted read-only in the default operating system, +so mounts must be created under the /mnt directory. + #### **--help** Print usage statement. @@ -72,6 +82,7 @@ $ podman machine init $ podman machine init myvm $ podman machine init --disk-size 50 $ podman machine init --memory=1024 myvm +$ podman machine init -v /Users:/mnt/Users ``` ## SEE ALSO diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 4f2947ac0..162ef43e2 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -18,6 +18,7 @@ type InitOptions struct { DiskSize uint64 IgnitionPath string ImagePath string + Volumes []string IsDefault bool Memory uint64 Name string diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go index 8404079a2..177487953 100644 --- a/pkg/machine/qemu/config.go +++ b/pkg/machine/qemu/config.go @@ -11,6 +11,8 @@ type MachineVM struct { CPUs uint64 // The command line representation of the qemu command CmdLine []string + // Mounts is the list of remote filesystems to mount + Mounts []Mount // IdentityPath is the fq path to the ssh priv key IdentityPath string // IgnitionFilePath is the fq path to the .ign file @@ -33,6 +35,13 @@ type MachineVM struct { RemoteUsername string } +type Mount struct { + Type string + Tag string + Source string + Target string +} + type Monitor struct { // Address portion of the qmp monitor (/tmp/tmp.sock) Address string diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index a80a11573..7bef4ff8e 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -167,6 +167,21 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { // Add arch specific options including image location v.CmdLine = append(v.CmdLine, v.addArchOptions()...) + mounts := []Mount{} + for i, volume := range opts.Volumes { + tag := fmt.Sprintf("vol%d", i) + paths := strings.SplitN(volume, ":", 2) + source := paths[0] + target := source + if len(paths) > 1 { + target = paths[1] + } + addVirtfsOptions := []string{"-virtfs", fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=mapped-xattr", source, tag)} + v.CmdLine = append(v.CmdLine, addVirtfsOptions...) + mounts = append(mounts, Mount{Type: "9p", Tag: tag, Source: source, Target: target}) + } + v.Mounts = mounts + // Add location of bootable image v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.ImagePath) // This kind of stinks but no other way around this r/n @@ -329,7 +344,28 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { return err } _, err = bufio.NewReader(conn).ReadString('\n') - return err + if err != nil { + return err + } + + if len(v.Mounts) > 0 { + for !v.isRunning() || !v.isListening() { + time.Sleep(100 * time.Millisecond) + } + } + for _, mount := range v.Mounts { + fmt.Printf("Mounting volume... %s:%s\n", mount.Source, mount.Target) + // create mountpoint directory if it doesn't exist + err = v.SSH(name, machine.SSHOptions{Args: []string{"-q", "--", "sudo", "mkdir", "-p", mount.Target}}) + if err != nil { + return err + } + err = v.SSH(name, machine.SSHOptions{Args: []string{"-q", "--", "sudo", "mount", "-t", mount.Type, "-o", "trans=virtio", mount.Tag, mount.Target, "-o", "version=9p2000.L,msize=131072"}}) + if err != nil { + return err + } + } + return nil } // Stop uses the qmp monitor to call a system_powerdown @@ -506,6 +542,16 @@ func (v *MachineVM) isRunning() bool { return true } +func (v *MachineVM) isListening() bool { + // Check if we can dial it + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "localhost", v.Port), 10*time.Millisecond) + if err != nil { + return false + } + conn.Close() + return true +} + // SSH opens an interactive SSH session to the vm specified. // Added ssh function to VM interface: pkg/machine/config/go : line 58 func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error { -- cgit v1.2.3-54-g00ecf From a3326e23d852fdcf03e7358f631ca24f5881ac70 Mon Sep 17 00:00:00 2001 From: Anders F Björklund Date: Sun, 31 Oct 2021 13:05:25 +0100 Subject: Check the mount type for future compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are other mount types available, such as NFS or SMB, or one could use reverse sshfs for better compatibility. It could either be a global option, or it could perhaps be overridden for each volume (like the container volumes). Refactor the creation of the options string or array. Allow specifying the volume as read-only, if desired. [NO NEW TESTS NEEDED] Signed-off-by: Anders F Björklund --- pkg/machine/qemu/config.go | 9 ++++---- pkg/machine/qemu/machine.go | 54 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go index 177487953..e76509bb1 100644 --- a/pkg/machine/qemu/config.go +++ b/pkg/machine/qemu/config.go @@ -36,10 +36,11 @@ type MachineVM struct { } type Mount struct { - Type string - Tag string - Source string - Target string + Type string + Tag string + Source string + Target string + ReadOnly bool } type Monitor struct { diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 7bef4ff8e..fde520f03 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -36,6 +36,11 @@ func GetQemuProvider() machine.Provider { return qemuProvider } +const ( + VolumeTypeVirtfs = "virtfs" + MountType9p = "9p" +) + // NewMachine initializes an instance of a virtual machine based on the qemu // virtualization. func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { @@ -167,18 +172,42 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { // Add arch specific options including image location v.CmdLine = append(v.CmdLine, v.addArchOptions()...) + // TODO: add to opts + volumeType := VolumeTypeVirtfs + mounts := []Mount{} for i, volume := range opts.Volumes { tag := fmt.Sprintf("vol%d", i) - paths := strings.SplitN(volume, ":", 2) + paths := strings.SplitN(volume, ":", 3) source := paths[0] target := source + readonly := false if len(paths) > 1 { target = paths[1] } - addVirtfsOptions := []string{"-virtfs", fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=mapped-xattr", source, tag)} - v.CmdLine = append(v.CmdLine, addVirtfsOptions...) - mounts = append(mounts, Mount{Type: "9p", Tag: tag, Source: source, Target: target}) + if len(paths) > 2 { + options := paths[2] + volopts := strings.Split(options, ",") + for _, o := range volopts { + switch o { + case "rw": + readonly = false + case "ro": + readonly = true + default: + fmt.Printf("Unknown option: %s\n", o) + } + } + } + switch volumeType { + case VolumeTypeVirtfs: + virtfsOptions := fmt.Sprintf("local,path=%s,mount_tag=%s,security_model=mapped-xattr", source, tag) + if readonly { + virtfsOptions += ",readonly" + } + v.CmdLine = append(v.CmdLine, []string{"-virtfs", virtfsOptions}...) + mounts = append(mounts, Mount{Type: MountType9p, Tag: tag, Source: source, Target: target, ReadOnly: readonly}) + } } v.Mounts = mounts @@ -360,9 +389,20 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { if err != nil { return err } - err = v.SSH(name, machine.SSHOptions{Args: []string{"-q", "--", "sudo", "mount", "-t", mount.Type, "-o", "trans=virtio", mount.Tag, mount.Target, "-o", "version=9p2000.L,msize=131072"}}) - if err != nil { - return err + switch mount.Type { + case MountType9p: + mountOptions := []string{"-t", "9p"} + mountOptions = append(mountOptions, []string{"-o", "trans=virtio", mount.Tag, mount.Target}...) + mountOptions = append(mountOptions, []string{"-o", "version=9p2000.L,msize=131072"}...) + if mount.ReadOnly { + mountOptions = append(mountOptions, []string{"-o", "ro"}...) + } + err = v.SSH(name, machine.SSHOptions{Args: append([]string{"-q", "--", "sudo", "mount"}, mountOptions...)}) + if err != nil { + return err + } + default: + return fmt.Errorf("unknown mount type: %s", mount.Type) } } return nil -- cgit v1.2.3-54-g00ecf From 6630e5cf66cf76aefcfe9caebe5df4f37dd0bdd5 Mon Sep 17 00:00:00 2001 From: Anders F Björklund Date: Mon, 13 Dec 2021 20:34:37 +0100 Subject: Make it possible to select the volume driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the same type of mounts for all the machine volumes. The default could change in the future, depending on OS. [NO NEW TESTS NEEDED] Signed-off-by: Anders F Björklund --- cmd/podman/machine/init.go | 4 ++++ docs/source/markdown/podman-machine-init.1.md | 4 ++++ pkg/machine/config.go | 1 + pkg/machine/qemu/machine.go | 12 ++++++++++-- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index b913a252e..ed04239c4 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -92,6 +92,10 @@ func init() { flags.StringArrayVarP(&initOpts.Volumes, VolumeFlagName, "v", []string{}, "Volumes to mount, source:target") _ = initCmd.RegisterFlagCompletionFunc(VolumeFlagName, completion.AutocompleteDefault) + VolumeDriverFlagName := "volume-driver" + flags.StringVar(&initOpts.VolumeDriver, VolumeDriverFlagName, "", "Optional volume driver") + _ = initCmd.RegisterFlagCompletionFunc(VolumeDriverFlagName, completion.AutocompleteDefault) + IgnitionPathFlagName := "ignition-path" flags.StringVar(&initOpts.IgnitionPath, IgnitionPathFlagName, "", "Path to ignition file") _ = initCmd.RegisterFlagCompletionFunc(IgnitionPathFlagName, completion.AutocompleteDefault) diff --git a/docs/source/markdown/podman-machine-init.1.md b/docs/source/markdown/podman-machine-init.1.md index b936447fb..b515e8763 100644 --- a/docs/source/markdown/podman-machine-init.1.md +++ b/docs/source/markdown/podman-machine-init.1.md @@ -71,6 +71,10 @@ Podman mounts _host-dir_ in the host to _machine-dir_ in the Podman machine. The root filesystem is mounted read-only in the default operating system, so mounts must be created under the /mnt directory. +#### **--volume-driver** + +Driver to use for mounting volumes from the host, such as `virtfs`. + #### **--help** Print usage statement. diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 162ef43e2..33a352898 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -19,6 +19,7 @@ type InitOptions struct { IgnitionPath string ImagePath string Volumes []string + VolumeDriver string IsDefault bool Memory uint64 Name string diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index fde520f03..f09107c71 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -172,8 +172,16 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { // Add arch specific options including image location v.CmdLine = append(v.CmdLine, v.addArchOptions()...) - // TODO: add to opts - volumeType := VolumeTypeVirtfs + var volumeType string + switch opts.VolumeDriver { + case "virtfs": + volumeType = VolumeTypeVirtfs + case "": // default driver + volumeType = VolumeTypeVirtfs + default: + err := fmt.Errorf("unknown volume driver: %s", opts.VolumeDriver) + return false, err + } mounts := []Mount{} for i, volume := range opts.Volumes { -- cgit v1.2.3-54-g00ecf