The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6971
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From 349316b7f8ba28cd4da262cfeb69d0d51e3b37ed Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 3 Mar 2020 08:08:59 +0000 Subject: [PATCH 1/9] lxd/device/disk: Validation error message quoting consistency Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/device/disk.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lxd/device/disk.go b/lxd/device/disk.go index c3c2c3a52d..80a8844a55 100644 --- a/lxd/device/disk.go +++ b/lxd/device/disk.go @@ -105,19 +105,19 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error { } if d.config["required"] != "" && d.config["optional"] != "" { - return fmt.Errorf("Cannot use both \"required\" and deprecated \"optional\" properties at the same time") + return fmt.Errorf(`Cannot use both "required" and deprecated "optional" properties at the same time`) } if d.config["source"] == "" && d.config["path"] != "/" { - return fmt.Errorf("Disk entry is missing the required \"source\" property") + return fmt.Errorf(`Disk entry is missing the required "source" property`) } if d.config["path"] == "/" && d.config["source"] != "" { - return fmt.Errorf("Root disk entry may not have a \"source\" property set") + return fmt.Errorf(`Root disk entry may not have a "source" property set`) } if d.config["path"] == "/" && d.config["pool"] == "" { - return fmt.Errorf("Root disk entry must have a \"pool\" property set") + return fmt.Errorf(`Root disk entry must have a "pool" property set`) } if d.config["size"] != "" && d.config["path"] != "/" { @@ -129,7 +129,7 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error { } if !(strings.HasPrefix(d.config["source"], "ceph:") || strings.HasPrefix(d.config["source"], "cephfs:")) && (d.config["ceph.cluster_name"] != "" || d.config["ceph.user_name"] != "") { - return fmt.Errorf("Invalid options ceph.cluster_name/ceph.user_name for source: %s", d.config["source"]) + return fmt.Errorf("Invalid options ceph.cluster_name/ceph.user_name for source %q", d.config["source"]) } // Check no other devices also have the same path as us. Use LocalDevices for this check so @@ -153,12 +153,12 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error { // for the existence of "source" when "pool" is empty. if d.config["pool"] == "" && d.config["source"] != "" && d.config["source"] != diskSourceCloudInit && d.isRequired(d.config) && !shared.PathExists(shared.HostPath(d.config["source"])) && !strings.HasPrefix(d.config["source"], "ceph:") && !strings.HasPrefix(d.config["source"], "cephfs:") { - return fmt.Errorf("Missing source '%s' for disk '%s'", d.config["source"], d.name) + return fmt.Errorf("Missing source %q for disk %q", d.config["source"], d.name) } if d.config["pool"] != "" { if d.config["shift"] != "" { - return fmt.Errorf("The \"shift\" property cannot be used with custom storage volumes") + return fmt.Errorf(`The "shift" property cannot be used with custom storage volumes`) } if filepath.IsAbs(d.config["source"]) { @@ -167,7 +167,7 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error { _, err := d.state.Cluster.StoragePoolGetID(d.config["pool"]) if err != nil { - return fmt.Errorf("The \"%s\" storage pool doesn't exist", d.config["pool"]) + return fmt.Errorf("The %q storage pool doesn't exist", d.config["pool"]) } // Only check storate volume is available if we are validating an instance device and not a profile From 94501ea6097f2d4471b99303674b82dc37f9b270 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 3 Mar 2020 08:15:50 +0000 Subject: [PATCH 2/9] lxd/device/disk: Adds support for adding directory source for VM 9p sharing Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/device/disk.go | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/lxd/device/disk.go b/lxd/device/disk.go index 80a8844a55..fb68703c35 100644 --- a/lxd/device/disk.go +++ b/lxd/device/disk.go @@ -88,15 +88,7 @@ func (d *disk) validateConfig(instConf instance.ConfigReader) error { "ceph.cluster_name": shared.IsAny, "ceph.user_name": shared.IsAny, "boot.priority": shared.IsUint32, - } - - // VMs don't use the "path" property, but containers need it, so if we are validating a profile that can - // be used for all instance types, we must allow any value. - if instConf.Type() == instancetype.Any { - rules["path"] = shared.IsAny - } else if instConf.Type() == instancetype.Container || d.config["path"] == "/" { - // If we are validating a container or the root device is being validated, then require the value. - rules["path"] = shared.IsNotEmpty + "path": shared.IsAny, } err := d.config.Validate(rules) @@ -393,21 +385,23 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) { } return &runConf, nil } else if d.config["source"] != "" { - // This is a normal disk device or image. + // This is a normal disk device, image or injected 9p directory share. if !shared.PathExists(shared.HostPath(d.config["source"])) { return nil, fmt.Errorf("Cannot find disk source") } - if shared.IsDir(shared.HostPath(d.config["source"])) { - return nil, fmt.Errorf("Only block devices and disk images can be attached to VMs") + mount := deviceConfig.MountEntryItem{ + DevPath: shared.HostPath(d.config["source"]), + DevName: d.name, } - runConf.Mounts = []deviceConfig.MountEntryItem{ - { - DevPath: shared.HostPath(d.config["source"]), - DevName: d.name, - }, + // If the source being added is a directory, then we will be using 9p directory sharing to mount + // the directory inside the VM, as such we need to indicate to the VM the target path to mount to. + if shared.IsDir(shared.HostPath(d.config["source"])) { + mount.TargetPath = d.config["path"] } + + runConf.Mounts = []deviceConfig.MountEntryItem{mount} return &runConf, nil } From a803980eefb712842ed11168078b2a1c3391828c Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 3 Mar 2020 10:38:31 +0000 Subject: [PATCH 3/9] lxd/instance/drivers/driver/qemu/templates: Adds 9p directory disk device template Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/drivers/driver_qemu_templates.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lxd/instance/drivers/driver_qemu_templates.go b/lxd/instance/drivers/driver_qemu_templates.go index 63e525d19e..8b2e599e44 100644 --- a/lxd/instance/drivers/driver_qemu_templates.go +++ b/lxd/instance/drivers/driver_qemu_templates.go @@ -200,6 +200,23 @@ fsdev = "qemu_config" mount_tag = "config" `)) +// Devices use "lxd_" prefix indicating that this is a internally named device. +var qemuDriveDir = template.Must(template.New("qemuDriveDir").Parse(` +# {{.devName}} drive +[fsdev "lxd_{{.devName}}"] +fsdriver = "proxy" +path = "{{.devPath}}" +sock_fd = "{{.proxyFD}}" +{{- if .readonly}} +readonly = "on" +{{- end}} + +[device "dev-lxd_{{.devName}}"] +driver = "virtio-9p-pci" +fsdev = "lxd_{{.devName}}" +mount_tag = "lxd_{{.devName}}" +`)) + // Devices use "lxd_" prefix indicating that this is a user named device. var qemuDrive = template.Must(template.New("qemuDrive").Parse(` # {{.devName}} drive From 2495e687d333220607d500b346c811a6d9d22045 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 3 Mar 2020 10:40:46 +0000 Subject: [PATCH 4/9] lxd/device/disk: Adds support for disk 9p directory share Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/device/disk.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lxd/device/disk.go b/lxd/device/disk.go index fb68703c35..25379fd8ad 100644 --- a/lxd/device/disk.go +++ b/lxd/device/disk.go @@ -399,6 +399,11 @@ func (d *disk) startVM() (*deviceConfig.RunConfig, error) { // the directory inside the VM, as such we need to indicate to the VM the target path to mount to. if shared.IsDir(shared.HostPath(d.config["source"])) { mount.TargetPath = d.config["path"] + mount.FSType = "9p" + + if shared.IsTrue(d.config["readonly"]) { + mount.Opts = append(mount.Opts, "ro") + } } runConf.Mounts = []deviceConfig.MountEntryItem{mount} From 3e90874139802e34c53832191b4200c2dd62499a Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 3 Mar 2020 10:43:47 +0000 Subject: [PATCH 5/9] lxd/instance/drivers/driver/qemu: Removes unused architecture var Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/drivers/driver_qemu.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 67e8a4dc66..613265640b 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -1699,12 +1699,11 @@ func (vm *qemu) addDriveConfig(sb *strings.Builder, bootIndexes map[string]int, } return qemuDrive.Execute(sb, map[string]interface{}{ - "architecture": vm.architectureName, - "devName": driveConf.DevName, - "devPath": driveConf.DevPath, - "bootIndex": bootIndexes[driveConf.DevName], - "cacheMode": cacheMode, - "aioMode": aioMode, + "devName": driveConf.DevName, + "devPath": driveConf.DevPath, + "bootIndex": bootIndexes[driveConf.DevName], + "cacheMode": cacheMode, + "aioMode": aioMode, }) } From f055168b4072ec9b404c4acf0af9f7b8489984a9 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 3 Mar 2020 10:41:31 +0000 Subject: [PATCH 6/9] lxd/instance/drivers/driver/qemu: Adds support for passing through unix socket FD to qemu Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/drivers/driver_qemu.go | 34 ++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 613265640b..c657a054a9 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -781,13 +781,41 @@ func (vm *qemu) Start(stateful bool) error { // Open any extra files and pass their file handles to qemu command. for _, file := range fdFiles { - f, err := os.OpenFile(file, os.O_RDWR, 0) + info, err := os.Stat(file) if err != nil { - err = errors.Wrapf(err, "Error opening exta file %q", file) + err = errors.Wrapf(err, "Error detecting file type %q", file) op.Done(err) return err } - defer f.Close() // Close file after qemu has started. + + var f *os.File + mode := info.Mode() + if mode&os.ModeSocket != 0 { + c, err := vm.openUnixSocket(file) + if err != nil { + err = errors.Wrapf(err, "Error opening socket file %q", file) + op.Done(err) + return err + } + + f, err = c.File() + if err != nil { + err = errors.Wrapf(err, "Error getting socket file descriptor %q", file) + op.Done(err) + return err + } + defer c.Close() + defer f.Close() // Close file after qemu has started. + } else { + f, err = os.OpenFile(file, os.O_RDWR, 0) + if err != nil { + err = errors.Wrapf(err, "Error opening exta file %q", file) + op.Done(err) + return err + } + defer f.Close() // Close file after qemu has started. + } + cmd.ExtraFiles = append(cmd.ExtraFiles, f) } From 4b55d454786fe07e5decf22885a98f90ba7148c6 Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 3 Mar 2020 10:41:53 +0000 Subject: [PATCH 7/9] lxd/instance/drivers/driver/qemu: Adds openUnixSocket function This is used for when passing unix socket FDs to qemu. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/drivers/driver_qemu.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index c657a054a9..90b0660691 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -927,6 +927,21 @@ func (vm *qemu) Start(stateful bool) error { return nil } +// openUnixSocket connects to a UNIX socket and returns the connection. +func (vm *qemu) openUnixSocket(sockPath string) (*net.UnixConn, error) { + addr, err := net.ResolveUnixAddr("unix", sockPath) + if err != nil { + return nil, err + } + + c, err := net.DialUnix("unix", nil, addr) + if err != nil { + return nil, err + } + + return c, nil +} + func (vm *qemu) setupNvram() error { // No UEFI nvram for ppc64le. if vm.architecture == osarch.ARCH_64BIT_POWERPC_LITTLE_ENDIAN { From 979fa9c67182259e875b956e919948b98a2d956f Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 3 Mar 2020 10:43:13 +0000 Subject: [PATCH 8/9] lxd/instance/drivers/driver/qemu: Adds addFileDescriptor function Keeps the logic of file descriptor counting inside a single function, as will now be used in multiple places. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/drivers/driver_qemu.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 90b0660691..0ee127be87 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -1680,6 +1680,14 @@ func (vm *qemu) addConfDriveConfig(sb *strings.Builder) error { }) } +// addFileDescriptor adds a file path to the list of files to open and pass file descriptor to qemu. +// Returns the file descriptor number that qemu will receive. +func (vm *qemu) addFileDescriptor(fdFiles *[]string, filePath string) int { + // Append the tap device file path to the list of files to be opened and passed to qemu. + *fdFiles = append(*fdFiles, filePath) + return 2 + len(*fdFiles) // Use 2+fdFiles count, as first user file descriptor is 3. +} + // addRootDriveConfig adds the qemu config required for adding the root drive. func (vm *qemu) addRootDriveConfig(sb *strings.Builder, bootIndexes map[string]int, rootDriveConf deviceConfig.MountEntryItem) error { if rootDriveConf.TargetPath != "/" { @@ -1790,8 +1798,7 @@ func (vm *qemu) addNetDevConfig(sb *strings.Builder, nicIndex int, bootIndexes m } // Append the tap device file path to the list of files to be opened and passed to qemu. - *fdFiles = append(*fdFiles, fmt.Sprintf("/dev/tap%d", ifindex)) - tplFields["tapFD"] = 2 + len(*fdFiles) // Use 2+fdFiles count, as first user file descriptor is 3. + tplFields["tapFD"] = vm.addFileDescriptor(fdFiles, fmt.Sprintf("/dev/tap%d", ifindex)) tpl = qemuNetdevTapFD } else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/tun_flags", nicName)) { // Detect TAP (via TUN driver) device. From 1c7f29495faa056b9e92198bf4f1881bfa3fc6aa Mon Sep 17 00:00:00 2001 From: Thomas Parrott <thomas.parr...@canonical.com> Date: Tue, 3 Mar 2020 10:42:30 +0000 Subject: [PATCH 9/9] lxd/instance/drivers/driver/qemu: Adds addDriveDirConfig function This is used to generate qemu config for 9p directory disk shares. Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com> --- lxd/instance/drivers/driver_qemu.go | 40 +++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 0ee127be87..9f74ad5a4b 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -688,7 +688,7 @@ func (vm *qemu) Start(stateful bool) error { // Define a set of files to open and pass their file descriptors to qemu command. fdFiles := make([]string, 0) - confFile, err := vm.generateQemuConfigFile(devConfs, &fdFiles) + confFile, err := vm.generateQemuConfigFile(devConfs, &fdFiles, revert) if err != nil { op.Done(err) return err @@ -1510,7 +1510,7 @@ func (vm *qemu) deviceBootPriorities() (map[string]int, error) { // generateQemuConfigFile writes the qemu config file and returns its location. // It writes the config file inside the VM's log path. -func (vm *qemu) generateQemuConfigFile(devConfs []*deviceConfig.RunConfig, fdFiles *[]string) (string, error) { +func (vm *qemu) generateQemuConfigFile(devConfs []*deviceConfig.RunConfig, fdFiles *[]string, reverter *revert.Reverter) (string, error) { var sb *strings.Builder = &strings.Builder{} err := qemuBase.Execute(sb, map[string]interface{}{ @@ -1564,6 +1564,8 @@ func (vm *qemu) generateQemuConfigFile(devConfs []*deviceConfig.RunConfig, fdFil for _, drive := range runConf.Mounts { if drive.TargetPath == "/" { err = vm.addRootDriveConfig(sb, bootIndexes, drive) + } else if drive.FSType == "9p" { + err = vm.addDriveDirConfig(sb, drive, fdFiles, reverter) } else { err = vm.addDriveConfig(sb, bootIndexes, drive) } @@ -1721,6 +1723,40 @@ func (vm *qemu) addRootDriveConfig(sb *strings.Builder, bootIndexes map[string]i return vm.addDriveConfig(sb, bootIndexes, driveConf) } +// addDriveDirConfig adds the qemu config required for adding a supplementary drive directory share. +func (vm *qemu) addDriveDirConfig(sb *strings.Builder, driveConf deviceConfig.MountEntryItem, fdFiles *[]string, reverter *revert.Reverter) error { + // Start the virtfs-proxy-helper process as root so that when the VM process is started + // as an unprivileged user, we can still share directories that process cannot access. + sockPath := filepath.Join(vm.DevicesPath(), fmt.Sprintf("%s.sock", driveConf.DevName)) + os.Remove(sockPath) + _, err := shared.RunCommand("virtfs-proxy-helper", "-u", "0", "-g", "0", "-s", sockPath, "-p", driveConf.DevPath) + if err != nil { + return err + } + + // If qemu fails to start, we should connect to the socket and then disconnect to stop the proxy process. + reverter.Add(func() { + if shared.PathExists(sockPath) { + s, err := vm.openUnixSocket(sockPath) + if err != nil { + return + } + + s.Close() + } + }) + + proxyFD := vm.addFileDescriptor(fdFiles, sockPath) + + return qemuDriveDir.Execute(sb, map[string]interface{}{ + "devName": driveConf.DevName, + "devPath": driveConf.DevPath, + "targetPath": driveConf.TargetPath, + "readonly": shared.StringInSlice("ro", driveConf.Opts), + "proxyFD": proxyFD, + }) +} + // addDriveConfig adds the qemu config required for adding a supplementary drive. func (vm *qemu) addDriveConfig(sb *strings.Builder, bootIndexes map[string]int, driveConf deviceConfig.MountEntryItem) error { // Use native kernel async IO and O_DIRECT by default.
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel