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