The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6456

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) ===
Adds the ability to attach a cloud-init config drive to a VM:

E.g.

```
lxc config device add v1 config disk source=cloud-init:config
```

This will generate and attach an ISO file containing the VM's cloud-init config variables in such a way that cloud-init inside the VM will detect it and action them.
From af3381639e6d2fab782797ca97b7e8a846515386 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 14 Nov 2019 13:36:44 +0000
Subject: [PATCH 1/9] lxd/device/disk: Adds support for generating VM config
 drive

- Splits deviceStart function into startContainer and startVM

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/device/disk.go | 41 ++++++++++++++++++++++++++++++++---------
 1 file changed, 32 insertions(+), 9 deletions(-)

diff --git a/lxd/device/disk.go b/lxd/device/disk.go
index a06fc8a81b..c68f6010cc 100644
--- a/lxd/device/disk.go
+++ b/lxd/device/disk.go
@@ -154,7 +154,7 @@ func (d *disk) validateConfig() error {
                                return fmt.Errorf("Check if volume is 
available: %v", err)
                        }
                        if !isAvailable {
-                               return fmt.Errorf("Storage volume %q is already 
attached to a container on a different node", d.config["source"])
+                               return fmt.Errorf("Storage volume %q is already 
attached to an instance on a different node", d.config["source"])
                        }
                }
        }
@@ -184,23 +184,23 @@ func (d *disk) CanHotPlug() (bool, []string) {
        return true, []string{"limits.max", "limits.read", "limits.write", 
"size"}
 }
 
-// Start is run when the device is added to the container.
+// Start is run when the device is added to the instance.
 func (d *disk) Start() (*RunConfig, error) {
        err := d.validateEnvironment()
        if err != nil {
                return nil, err
        }
 
-       runConf := RunConfig{}
-
        if d.instance.Type() == instancetype.VM {
-               if shared.IsRootDiskDevice(d.config) {
-                       return &runConf, nil
-               }
-
-               return nil, fmt.Errorf("Non-root disks not supported for VMs")
+               return d.startVM()
        }
 
+       return d.startContainer()
+}
+
+// startVM starts the disk device for a container instance.
+func (d *disk) startContainer() (*RunConfig, error) {
+       runConf := RunConfig{}
        isReadOnly := shared.IsTrue(d.config["readonly"])
 
        // Apply cgroups only after all the mounts have been processed.
@@ -334,6 +334,29 @@ func (d *disk) Start() (*RunConfig, error) {
        return &runConf, nil
 }
 
+// startVM starts the disk device for a virtual machine instance.
+func (d *disk) startVM() (*RunConfig, error) {
+       runConf := RunConfig{}
+
+       if shared.IsRootDiskDevice(d.config) {
+               runConf.RootFS.Path = d.config["path"]
+               return &runConf, nil
+       }
+
+       // This is a virtual disk source that can be attached to a VM to 
provide cloud-init config.
+       if d.config["source"] == "cloud-init:config" {
+               runConf.Mounts = []MountEntryItem{
+                       {
+                               DevPath:    "foo", // Path to generated iso 
file.
+                               TargetPath: d.name,
+                       },
+               }
+               return &runConf, nil
+       }
+
+       return nil, fmt.Errorf("Disk type not supported for VMs")
+}
+
 // postStart is run after the instance is started.
 func (d *disk) postStart() error {
        devPath := d.getDevicePath(d.name, d.config)

From 9037a96abcf871a7e215d0169dedbafebacea180 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 14 Nov 2019 13:37:23 +0000
Subject: [PATCH 2/9] lxd/device/nic/bridged: Adds hwaddr to runConf when
 instance type is VM

This allows it to be used inside the VM config to set the tap device to the 
correct MAC address.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/device/nic_bridged.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index 8fd6aa589b..8b18cdb308 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -177,6 +177,12 @@ func (d *nicBridged) Start() (*RunConfig, error) {
                {Key: "link", Value: peerName},
        }
 
+       if d.instance.Type() == instancetype.VM {
+               runConf.NetworkInterface = append(runConf.NetworkInterface,
+                       RunConfigItem{Key: "hwaddr", Value: d.config["hwaddr"]},
+               )
+       }
+
        return &runConf, nil
 }
 

From 7742544dfadba7cfc2daf86312d87c7638b2ba45 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 14 Nov 2019 10:56:04 +0000
Subject: [PATCH 3/9] lxd/vm/qemu: Modifies qemu config generation to support
 dynamic devices

- Network
- Supplemental drives

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/vm_qemu.go | 109 +++++++++++++++++++++++++++++++++++++------------
 1 file changed, 84 insertions(+), 25 deletions(-)

diff --git a/lxd/vm_qemu.go b/lxd/vm_qemu.go
index 8fa3b683b3..a13b4bdc4f 100644
--- a/lxd/vm_qemu.go
+++ b/lxd/vm_qemu.go
@@ -440,7 +440,7 @@ func (vm *vmQemu) Start(stateful bool) error {
                }
        }
 
-       tapDev := map[string]string{}
+       devConfs := make([]*device.RunConfig, 0, len(vm.expandedDevices))
 
        // Setup devices in sorted order, this ensures that device mounts are 
added in path order.
        for _, dev := range vm.expandedDevices.Sorted() {
@@ -454,18 +454,10 @@ func (vm *vmQemu) Start(stateful bool) error {
                        continue
                }
 
-               if len(runConf.NetworkInterface) > 0 {
-                       for _, nicItem := range runConf.NetworkInterface {
-                               if nicItem.Key == "link" {
-                                       tapDev["tap"] = nicItem.Value
-                                       tapDev["hwaddr"] = 
vm.localConfig[fmt.Sprintf("volatile.%s.hwaddr", dev.Name)]
-                               }
-                       }
-
-               }
+               devConfs = append(devConfs, runConf)
        }
 
-       confFile, err := vm.generateQemuConfigFile(tapDev)
+       confFile, err := vm.generateQemuConfigFile(devConfs)
        if err != nil {
                return err
        }
@@ -768,7 +760,7 @@ WantedBy=multi-user.target
 }
 
 // generateQemuConfigFile writes the qemu config file and returns its location.
-func (vm *vmQemu) generateQemuConfigFile(tapDev map[string]string) (string, 
error) {
+func (vm *vmQemu) generateQemuConfigFile(devConfs []*device.RunConfig) 
(string, error) {
        var sb *strings.Builder = &strings.Builder{}
 
        // Base config. This is common for all VMs and has no variables in it.
@@ -858,11 +850,6 @@ backend = "pty"
                return "", err
        }
 
-       err = vm.addRootDriveConfig(sb)
-       if err != nil {
-               return "", err
-       }
-
        err = vm.addCPUConfig(sb)
        if err != nil {
                return "", err
@@ -872,13 +859,39 @@ backend = "pty"
        vm.addVsockConfig(sb)
        vm.addMonitorConfig(sb)
        vm.addConfDriveConfig(sb)
-       vm.addNetConfig(sb, tapDev)
+
+       for _, runConf := range devConfs {
+               // Add root drive device.
+               if runConf.RootFS.Path != "" {
+                       err = vm.addRootDriveConfig(sb)
+                       if err != nil {
+                               return "", err
+                       }
+               }
+
+               // Add drive devices.
+               if len(runConf.Mounts) > 0 {
+                       driveIndex := 0
+                       for _, drive := range runConf.Mounts {
+                               // Increment so index starts at 1, as root 
drive uses index 0.
+                               driveIndex++
+
+                               vm.addDriveConfig(sb, driveIndex, drive)
+                       }
+               }
+
+               // Add network device.
+               if len(runConf.NetworkInterface) > 0 {
+                       vm.addNetDevConfig(sb, runConf.NetworkInterface)
+               }
+       }
 
        // Write the config file to disk.
        configPath := filepath.Join(vm.LogPath(), "qemu.conf")
        return configPath, ioutil.WriteFile(configPath, []byte(sb.String()), 
0640)
 }
 
+// addMemoryConfig adds the qemu config required for setting the size of the 
VM's memory.
 func (vm *vmQemu) addMemoryConfig(sb *strings.Builder) error {
        // Configure memory limit.
        memSize := vm.expandedConfig["limits.memory"]
@@ -902,6 +915,7 @@ size = "%dK"
        return nil
 }
 
+// addVsockConfig adds the qemu config required for setting up the host->VM 
vsock socket.
 func (vm *vmQemu) addVsockConfig(sb *strings.Builder) {
        vsockID := vm.vsockID()
 
@@ -924,6 +938,7 @@ addr = "0x0"
        return
 }
 
+// addCPUConfig adds the qemu config required for setting the number of 
virtualised CPUs.
 func (vm *vmQemu) addCPUConfig(sb *strings.Builder) error {
        // Configure CPU limit. TODO add control of sockets, cores and threads.
        cpus := vm.expandedConfig["limits.cpu"]
@@ -948,6 +963,7 @@ cpus = "%d"
        return nil
 }
 
+// addMonitorConfig adds the qemu config required for setting up the host side 
VM monitor device.
 func (vm *vmQemu) addMonitorConfig(sb *strings.Builder) {
        monitorPath := vm.getMonitorPath()
 
@@ -967,6 +983,7 @@ mode = "control"
        return
 }
 
+// addFirmwareConfig adds the qemu config required for adding a secure boot 
compatible EFI firmware.
 func (vm *vmQemu) addFirmwareConfig(sb *strings.Builder) {
        nvramPath := vm.getNvramPath()
 
@@ -989,6 +1006,7 @@ unit = "1"
        return
 }
 
+// addRootDriveConfig adds the qemu config required for adding the root drive.
 func (vm *vmQemu) addRootDriveConfig(sb *strings.Builder) error {
        pool, err := storagePools.GetPoolByInstance(vm.state, vm)
        if err != nil {
@@ -1022,28 +1040,66 @@ bootindex = "1"
        return nil
 }
 
+// addConfDriveConfig adds the qemu config required for adding the config 
drive.
 func (vm *vmQemu) addConfDriveConfig(sb *strings.Builder) {
        sb.WriteString(fmt.Sprintf(`
 # Config drive
-[fsdev "qemu_config"]
+[fsdev "lxd_config"]
 fsdriver = "local"
 security_model = "none"
 readonly = "on"
 path = "%s"
 
-[device "dev-qemu_config"]
+[device "dev-lxd_config"]
 driver = "virtio-9p-pci"
-fsdev = "qemu_config"
+fsdev = "lxd_config"
 mount_tag = "config"
 `, filepath.Join(vm.Path(), "config")))
 
        return
 }
 
-func (vm *vmQemu) addNetConfig(sb *strings.Builder, tapDev map[string]string) {
+// addDriveConfig adds the qemu config required for adding a supplementary 
drive.
+func (vm *vmQemu) addDriveConfig(sb *strings.Builder, driveIndex int, 
driveConf device.MountEntryItem) {
+       driveName := fmt.Sprintf(driveConf.TargetPath)
+
+       sb.WriteString(fmt.Sprintf(`
+# %s drive
+[drive "lxd_disk_%s"]
+file = "%s"
+format = "raw"
+if = "none"
+cache = "none"
+aio = "native"
+
+[device "dev-lxd_disk_%s"]
+driver = "scsi-hd"
+bus = "qemu_scsi.0"
+channel = "0"
+scsi-id = "%d"
+lun = "1"
+drive = "lxd_disk_%s"
+`, driveName, driveName, driveConf.DevPath, driveName, driveIndex, driveName))
+
+       return
+}
+
+// addNetDevConfig adds the qemu config required for adding a network device.
+func (vm *vmQemu) addNetDevConfig(sb *strings.Builder, nicConfig 
[]device.RunConfigItem) {
+       var devName, devTap, devHwaddr string
+       for _, nicItem := range nicConfig {
+               if nicItem.Key == "name" {
+                       devName = nicItem.Value
+               } else if nicItem.Key == "link" {
+                       devTap = nicItem.Value
+               } else if nicItem.Key == "hwaddr" {
+                       devHwaddr = nicItem.Value
+               }
+       }
+
        sb.WriteString(fmt.Sprintf(`
-# Network card ("eth0" device)
-[netdev "lxd_eth0"]
+# Network card ("%s" device)
+[netdev "lxd_%s"]
 type = "tap"
 ifname = "%s"
 script = "no"
@@ -1063,15 +1119,17 @@ mac = "%s"
 bus = "qemu_pcie5"
 addr = "0x0"
 bootindex = "2""
-`, tapDev["tap"], tapDev["hwaddr"]))
+`, devName, devName, devTap, devHwaddr))
 
        return
 }
 
+// pidFilePath returns the path where the qemu process should write its PID.
 func (vm *vmQemu) pidFilePath() string {
        return filepath.Join(vm.LogPath(), "qemu.pid")
 }
 
+// pid gets the PID of the running qemu process.
 func (vm *vmQemu) pid() (int, error) {
        pidStr, err := ioutil.ReadFile(vm.pidFilePath())
        if os.IsNotExist(err) {
@@ -1090,6 +1148,7 @@ func (vm *vmQemu) pid() (int, error) {
        return pid, nil
 }
 
+// Stop stops the VM.
 func (vm *vmQemu) Stop(stateful bool) error {
        if stateful {
                return fmt.Errorf("Stateful stop isn't supported for VMs at 
this time")

From 48633beed2bdc03dfb2b55bdae7230f53d5d7c01 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 14 Nov 2019 15:17:45 +0000
Subject: [PATCH 4/9] lxd/container: Renames containerValidDevices to
 instanceValidDevices

And updates to take an instancetype.Type argument so that validation can be 
instance type specific.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container.go | 41 +++++++++++++++++++++++++++++++----------
 1 file changed, 31 insertions(+), 10 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 81937a8843..9652a4ba11 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -195,28 +195,49 @@ func containerValidConfig(sysOS *sys.OS, config 
map[string]string, profile bool,
        return nil
 }
 
-// containerValidDevices validate container device configs.
-func containerValidDevices(state *state.State, cluster *db.Cluster, 
instanceName string, devices deviceConfig.Devices, expanded bool) error {
+// instanceValidDevices validate instance device configs.
+func instanceValidDevices(state *state.State, cluster *db.Cluster, 
instanceType instancetype.Type, instanceName string, devices 
deviceConfig.Devices, expanded bool) error {
        // Empty device list
        if devices == nil {
                return nil
        }
 
-       // Create a temporary containerLXC struct to use as an Instance in 
device validation.
+       // Create a temporary Instance for use in device validation.
        // Populate it's name, localDevices and expandedDevices properties 
based on the mode of
        // validation occurring. In non-expanded validation expensive checks 
should be avoided.
-       instance := &containerLXC{
-               name:         instanceName,
-               localDevices: devices.Clone(), // Prevent devices from 
modifying their config.
-       }
+       var inst Instance
 
-       if expanded {
-               instance.expandedDevices = instance.localDevices // Avoid 
another clone.
+       if instanceType == instancetype.Container {
+               c := &containerLXC{
+                       dbType:       instancetype.Container,
+                       name:         instanceName,
+                       localDevices: devices.Clone(), // Prevent devices from 
modifying their config.
+               }
+
+               if expanded {
+                       c.expandedDevices = c.localDevices // Avoid another 
clone.
+               }
+
+               inst = c
+       } else if instanceType == instancetype.VM {
+               vm := &vmQemu{
+                       dbType:       instancetype.VM,
+                       name:         instanceName,
+                       localDevices: devices.Clone(), // Prevent devices from 
modifying their config.
+               }
+
+               if expanded {
+                       vm.expandedDevices = vm.localDevices // Avoid another 
clone.
+               }
+
+               inst = vm
+       } else {
+               return fmt.Errorf("Invalid instance type")
        }
 
        // Check each device individually using the device package.
        for name, config := range devices {
-               _, err := device.New(instance, state, name, config, nil, nil)
+               _, err := device.New(inst, state, name, config, nil, nil)
                if err != nil {
                        return err
                }

From d79911116814e173187fe8699aedec0b80a2c13e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 14 Nov 2019 15:18:38 +0000
Subject: [PATCH 5/9] lxd/device/device/instance: Adds Path() to Instance
 interface

So can be used by disk device.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/device/device_instance.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/device/device_instance.go b/lxd/device/device_instance.go
index 296bcb2bdc..5063939d54 100644
--- a/lxd/device/device_instance.go
+++ b/lxd/device/device_instance.go
@@ -12,6 +12,7 @@ type Instance interface {
        Name() string
        Type() instancetype.Type
        Project() string
+       Path() string
        DevicesPath() string
        RootfsPath() string
        LogPath() string

From 072bee6c61130619f4047c3b3fb399cd3d60c872 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 14 Nov 2019 15:23:05 +0000
Subject: [PATCH 6/9] lxd/device/disk: Adds support for generating VM
 cloud-init config drive

Adds support for a special VM disk device with a source of "cloud-init:config".

        lxc config device add v1 config disk source=cloud-init:config

This generates an ISO containing the cloud-init config with a label of "cidata" 
so that cloud-init inside the VM detects it.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/device/disk.go | 82 +++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 78 insertions(+), 4 deletions(-)

diff --git a/lxd/device/disk.go b/lxd/device/disk.go
index c68f6010cc..107d8405be 100644
--- a/lxd/device/disk.go
+++ b/lxd/device/disk.go
@@ -22,6 +22,9 @@ import (
        "github.com/lxc/lxd/shared/units"
 )
 
+// Special disk "source" value used for generating a VM cloud-init config ISO.
+const diskSourceCloudInit = "cloud-init:config"
+
 type diskBlockLimit struct {
        readBps   int64
        readIops  int64
@@ -62,7 +65,6 @@ func (d *disk) validateConfig() error {
        }
 
        rules := map[string]func(string) error{
-               "path":              shared.IsNotEmpty,
                "required":          shared.IsBool,
                "optional":          shared.IsBool, // "optional" is 
deprecated, replaced by "required".
                "readonly":          shared.IsBool,
@@ -78,6 +80,11 @@ func (d *disk) validateConfig() error {
                "raw.mount.options": shared.IsAny,
        }
 
+       // VMs can have a special cloud-init config drive attached with no path.
+       if d.instance.Type() != instancetype.VM || d.config["source"] != 
diskSourceCloudInit {
+               rules["path"] = shared.IsNotEmpty
+       }
+
        err := d.config.Validate(rules)
        if err != nil {
                return err
@@ -126,7 +133,7 @@ func (d *disk) validateConfig() error {
        // When we want to attach a storage volume created via the storage api 
the "source" only
        // contains the name of the storage volume, not the path where it is 
mounted. So only check
        // for the existence of "source" when "pool" is empty.
-       if d.config["pool"] == "" && d.config["source"] != "" && 
d.isRequired(d.config) && 
!shared.PathExists(shared.HostPath(d.config["source"])) {
+       if d.config["pool"] == "" && d.config["source"] != "" && 
d.config["source"] != diskSourceCloudInit && d.isRequired(d.config) && 
!shared.PathExists(shared.HostPath(d.config["source"])) {
                return fmt.Errorf("Missing source '%s' for disk '%s'", 
d.config["source"], d.name)
        }
 
@@ -344,10 +351,15 @@ func (d *disk) startVM() (*RunConfig, error) {
        }
 
        // This is a virtual disk source that can be attached to a VM to 
provide cloud-init config.
-       if d.config["source"] == "cloud-init:config" {
+       if d.config["source"] == diskSourceCloudInit {
+               isoPath, err := d.generateVMConfigDrive()
+               if err != nil {
+                       return nil, err
+               }
+
                runConf.Mounts = []MountEntryItem{
                        {
-                               DevPath:    "foo", // Path to generated iso 
file.
+                               DevPath:    isoPath,
                                TargetPath: d.name,
                        },
                }
@@ -1035,3 +1047,65 @@ func (d *disk) getParentBlocks(path string) ([]string, 
error) {
 
        return devices, nil
 }
+
+// generateVMConfigDrive generates an ISO containing the cloud init config for 
a VM.
+// Returns the path to the ISO.
+func (d *disk) generateVMConfigDrive() (string, error) {
+       configDrivePath := filepath.Join(d.instance.Path(), "configdrv")
+
+       // Create config drive dir.
+       err := os.MkdirAll(configDrivePath, 0100)
+       if err != nil {
+               return "", err
+       }
+
+       // Add config drive mount instructions to cloud init.
+       vendorData := `#cloud-config
+runcmd:
+ - "mkdir /media/lxd_config"
+ - "mount -o ro -t iso9660 /dev/disk/by-label/cidata /media/lxd_config"
+ - "cp /media/lxd_config/media-lxd_config.mount /etc/systemd/system/"
+ - "systemctl enable media-lxd_config.mount"`
+
+       err = ioutil.WriteFile(filepath.Join(configDrivePath, "vendor-data"), 
[]byte(vendorData), 0400)
+       if err != nil {
+               return "", err
+       }
+
+       instanceConfig := d.instance.ExpandedConfig()
+       userData := instanceConfig["user.user-data"]
+
+       // Use an empty user-data file if no custom user-data supplied.
+       if userData == "" {
+               userData = "#cloud-config"
+       }
+
+       err = ioutil.WriteFile(filepath.Join(configDrivePath, "/user-data"), 
[]byte(userData), 0400)
+       if err != nil {
+               return "", err
+       }
+
+       metaData := fmt.Sprintf(`instance-id: %s
+local-hostname: %s
+`, d.instance.Name(), d.instance.Name())
+
+       err = ioutil.WriteFile(filepath.Join(configDrivePath, "meta-data"), 
[]byte(metaData), 0400)
+       if err != nil {
+               return "", err
+       }
+
+       // Finally convert the config drive dir into an ISO file. The cidata 
label is important
+       // as this is what cloud-init uses to detect, mount the drive and run 
the cloud-init
+       // templates on first boot. The vendor-data template then modifies the 
system so that the
+       // config drive is mounted and the agent is started on subsequent boots.
+       isoPath := filepath.Join(d.instance.Path(), "config.iso")
+       _, err = shared.RunCommand("mkisofs", "-R", "-V", "cidata", "-o", 
isoPath, configDrivePath)
+       if err != nil {
+               return "", err
+       }
+
+       // Remove the config drive folder.
+       os.RemoveAll(configDrivePath)
+
+       return isoPath, nil
+}

From 96ce20bab87b21bd7672d305e080eb736e0976ae Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 14 Nov 2019 15:25:10 +0000
Subject: [PATCH 7/9] lxd: Updates instanceValidDevices usage

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container.go      | 2 +-
 lxd/container_lxc.go  | 6 +++---
 lxd/profiles.go       | 6 ++++--
 lxd/profiles_utils.go | 6 ++++--
 lxd/vm_qemu.go        | 6 +++---
 5 files changed, 15 insertions(+), 11 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 9652a4ba11..fde7831bac 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -876,7 +876,7 @@ func instanceCreateInternal(s *state.State, args 
db.InstanceArgs) (Instance, err
        }
 
        // Validate container devices with the supplied container name and 
devices.
-       err = containerValidDevices(s, s.Cluster, args.Name, args.Devices, 
false)
+       err = instanceValidDevices(s, s.Cluster, args.Type, args.Name, 
args.Devices, false)
        if err != nil {
                return nil, errors.Wrap(err, "Invalid devices")
        }
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 32bc30e175..7edf6d3beb 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -295,7 +295,7 @@ func containerLXCCreate(s *state.State, args 
db.InstanceArgs) (container, error)
                return nil, err
        }
 
-       err = containerValidDevices(s, s.Cluster, c.Name(), c.expandedDevices, 
true)
+       err = instanceValidDevices(s, s.Cluster, c.Type(), c.Name(), 
c.expandedDevices, true)
        if err != nil {
                c.Delete()
                logger.Error("Failed creating container", ctxMap)
@@ -4130,7 +4130,7 @@ func (c *containerLXC) Update(args db.InstanceArgs, 
userRequested bool) error {
        }
 
        // Validate the new devices without using expanded devices validation 
(expensive checks disabled).
-       err = containerValidDevices(c.state, c.state.Cluster, c.Name(), 
args.Devices, false)
+       err = instanceValidDevices(c.state, c.state.Cluster, c.Type(), 
c.Name(), args.Devices, false)
        if err != nil {
                return errors.Wrap(err, "Invalid devices")
        }
@@ -4321,7 +4321,7 @@ func (c *containerLXC) Update(args db.InstanceArgs, 
userRequested bool) error {
        }
 
        // Do full expanded validation of the devices diff.
-       err = containerValidDevices(c.state, c.state.Cluster, c.Name(), 
c.expandedDevices, true)
+       err = instanceValidDevices(c.state, c.state.Cluster, c.Type(), 
c.Name(), c.expandedDevices, true)
        if err != nil {
                return errors.Wrap(err, "Invalid expanded devices")
        }
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 424a9e0b47..91b488dc6d 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -15,6 +15,7 @@ import (
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
        deviceConfig "github.com/lxc/lxd/lxd/device/config"
+       "github.com/lxc/lxd/lxd/instance/instancetype"
        "github.com/lxc/lxd/lxd/response"
        "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
@@ -107,8 +108,9 @@ func profilesPost(d *Daemon, r *http.Request) 
response.Response {
                return response.BadRequest(err)
        }
 
-       // Validate container devices with an empty instanceName to indicate 
profile validation.
-       err = containerValidDevices(d.State(), d.cluster, "", 
deviceConfig.NewDevices(req.Devices), false)
+       // Validate instance devices with an empty instanceName to indicate 
profile validation.
+       // At this point we don't know the instance type, so just use Container 
type for validation.
+       err = instanceValidDevices(d.State(), d.cluster, 
instancetype.Container, "", deviceConfig.NewDevices(req.Devices), false)
        if err != nil {
                return response.BadRequest(err)
        }
diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
index c483af6f5b..3f8d8d5a54 100644
--- a/lxd/profiles_utils.go
+++ b/lxd/profiles_utils.go
@@ -7,6 +7,7 @@ import (
        "github.com/lxc/lxd/lxd/db"
        "github.com/lxc/lxd/lxd/db/query"
        deviceConfig "github.com/lxc/lxd/lxd/device/config"
+       "github.com/lxc/lxd/lxd/instance/instancetype"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
        "github.com/pkg/errors"
@@ -19,8 +20,9 @@ func doProfileUpdate(d *Daemon, project, name string, id 
int64, profile *api.Pro
                return err
        }
 
-       // Validate container devices with an empty instanceName to indicate 
profile validation.
-       err = containerValidDevices(d.State(), d.cluster, "", 
deviceConfig.NewDevices(req.Devices), false)
+       // Validate instance devices with an empty instanceName to indicate 
profile validation.
+       // At this point we don't know the instance type, so just use Container 
type for validation.
+       err = instanceValidDevices(d.State(), d.cluster, 
instancetype.Container, "", deviceConfig.NewDevices(req.Devices), false)
        if err != nil {
                return err
        }
diff --git a/lxd/vm_qemu.go b/lxd/vm_qemu.go
index a13b4bdc4f..263f660544 100644
--- a/lxd/vm_qemu.go
+++ b/lxd/vm_qemu.go
@@ -172,7 +172,7 @@ func vmQemuCreate(s *state.State, args db.InstanceArgs) 
(Instance, error) {
                return nil, err
        }
 
-       err = containerValidDevices(s, s.Cluster, vm.Name(), 
vm.expandedDevices, true)
+       err = instanceValidDevices(s, s.Cluster, vm.Type(), vm.Name(), 
vm.expandedDevices, true)
        if err != nil {
                logger.Error("Failed creating instance", ctxMap)
                return nil, errors.Wrap(err, "Invalid devices")
@@ -1262,7 +1262,7 @@ func (vm *vmQemu) Update(args db.InstanceArgs, 
userRequested bool) error {
        }
 
        // Validate the new devices without using expanded devices validation 
(expensive checks disabled).
-       err = containerValidDevices(vm.state, vm.state.Cluster, vm.Name(), 
args.Devices, false)
+       err = instanceValidDevices(vm.state, vm.state.Cluster, vm.Type(), 
vm.Name(), args.Devices, false)
        if err != nil {
                return errors.Wrap(err, "Invalid devices")
        }
@@ -1446,7 +1446,7 @@ func (vm *vmQemu) Update(args db.InstanceArgs, 
userRequested bool) error {
        }
 
        // Do full expanded validation of the devices diff.
-       err = containerValidDevices(vm.state, vm.state.Cluster, vm.Name(), 
vm.expandedDevices, true)
+       err = instanceValidDevices(vm.state, vm.state.Cluster, vm.Type(), 
vm.Name(), vm.expandedDevices, true)
        if err != nil {
                return errors.Wrap(err, "Invalid expanded devices")
        }

From 96f32d5c7e7d58ca961b7a36622829c7e17a46ef Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 14 Nov 2019 15:51:54 +0000
Subject: [PATCH 8/9] lxd: Fixes bug in fillNetworkDevice volatile hwaddr
 generation

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_lxc.go | 3 ++-
 lxd/vm_qemu.go       | 4 +++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 7edf6d3beb..a364ac8a0b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -6485,6 +6485,7 @@ func (c *containerLXC) removeUnixDevices() error {
 // fillNetworkDevice takes a nic or infiniband device type and enriches it 
with automatically
 // generated name and hwaddr properties if these are missing from the device.
 func (c *containerLXC) fillNetworkDevice(name string, m deviceConfig.Device) 
(deviceConfig.Device, error) {
+       var err error
        newDevice := m.Clone()
 
        // Function to try and guess an available name
@@ -6578,7 +6579,7 @@ func (c *containerLXC) fillNetworkDevice(name string, m 
deviceConfig.Device) (de
                volatileHwaddr := c.localConfig[configKey]
                if volatileHwaddr == "" {
                        // Generate a new MAC address
-                       volatileHwaddr, err := deviceNextInterfaceHWAddr()
+                       volatileHwaddr, err = deviceNextInterfaceHWAddr()
                        if err != nil {
                                return nil, err
                        }
diff --git a/lxd/vm_qemu.go b/lxd/vm_qemu.go
index 263f660544..e0332ca7ff 100644
--- a/lxd/vm_qemu.go
+++ b/lxd/vm_qemu.go
@@ -2478,6 +2478,8 @@ func (vm *vmQemu) DaemonState() *state.State {
 // fillNetworkDevice takes a nic or infiniband device type and enriches it 
with automatically
 // generated name and hwaddr properties if these are missing from the device.
 func (vm *vmQemu) fillNetworkDevice(name string, m deviceConfig.Device) 
(deviceConfig.Device, error) {
+       var err error
+
        newDevice := m.Clone()
        updateKey := func(key string, value string) error {
                tx, err := vm.state.Cluster.Begin()
@@ -2505,7 +2507,7 @@ func (vm *vmQemu) fillNetworkDevice(name string, m 
deviceConfig.Device) (deviceC
                volatileHwaddr := vm.localConfig[configKey]
                if volatileHwaddr == "" {
                        // Generate a new MAC address
-                       volatileHwaddr, err := deviceNextInterfaceHWAddr()
+                       volatileHwaddr, err = deviceNextInterfaceHWAddr()
                        if err != nil {
                                return nil, err
                        }

From a09b5a4efb9581f772cf9a23ee20b1718bdc70d4 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 14 Nov 2019 16:02:55 +0000
Subject: [PATCH 9/9] lxd/vm/qemu: Fix root disk path in device

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/vm_qemu.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/vm_qemu.go b/lxd/vm_qemu.go
index e0332ca7ff..14707f7d24 100644
--- a/lxd/vm_qemu.go
+++ b/lxd/vm_qemu.go
@@ -1021,8 +1021,8 @@ func (vm *vmQemu) addRootDriveConfig(sb *strings.Builder) 
error {
        sb.WriteString(fmt.Sprintf(`
 # Root drive ("root" device)
 [drive "lxd_root"]
-file = "raw"
-format = "%s"
+file = "%s"
+format = "raw"
 if = "none"
 cache = "none"
 aio = "native"
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to