[lxc-devel] [lxd/master] VM cloud-init config drive

tomponline on Github lxc-bot at linuxcontainers.org
Thu Nov 14 16:04:41 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 614 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191114/a0da3ff4/attachment.bin>
-------------- next part --------------
From af3381639e6d2fab782797ca97b7e8a846515386 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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"


More information about the lxc-devel mailing list