Skip to content

Commit

Permalink
Merge pull request #13118 from hamistao/issue13063/debug_copying_cont…
Browse files Browse the repository at this point in the history
…ainer_with_device

LXC: Implement profile expansion on lxc copy
  • Loading branch information
tomponline authored Mar 27, 2024
2 parents 55cc7d1 + 28163ad commit c83a912
Show file tree
Hide file tree
Showing 46 changed files with 1,847 additions and 2,016 deletions.
79 changes: 56 additions & 23 deletions lxc/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func (c *cmdCopy) copyInstance(conf *config.Config, sourceResource string, destR
configMap[key] = value
}

deviceMap, err := parseDeviceOverrides(c.flagDevice)
deviceOverrides, err := parseDeviceOverrides(c.flagDevice)
if err != nil {
return err
}
Expand Down Expand Up @@ -181,23 +181,40 @@ func (c *cmdCopy) copyInstance(conf *config.Config, sourceResource string, destR
entry.Profiles = []string{}
}

// Allow setting additional config keys
for key, value := range configMap {
entry.Config[key] = value
// Check to see if any of the overridden devices are for devices that are not yet defined in the
// local devices (and thus maybe expected to be coming from profiles).
needProfileExpansion := false
for deviceName := range deviceOverrides {
_, isLocalDevice := entry.Devices[deviceName]
if !isLocalDevice {
needProfileExpansion = true
break
}
}

// Allow setting device overrides
for k, m := range deviceMap {
if entry.Devices[k] == nil {
entry.Devices[k] = m
continue
}
profileDevices := make(map[string]map[string]string)

for key, value := range m {
entry.Devices[k][key] = value
// If there are device overrides that are expected to be applied to profile devices then perform
// profile expansion.
if needProfileExpansion {
// If the list of profiles is empty then LXD would apply the default profile on the server side.
profileDevices, err = getProfileDevices(dest, entry.Profiles)
if err != nil {
return err
}
}

// Apply device overrides.
entry.Devices, err = shared.ApplyDeviceOverrides(profileDevices, entry.Devices, deviceOverrides)
if err != nil {
return err
}

// Allow setting additional config keys.
for key, value := range configMap {
entry.Config[key] = value
}

// Allow overriding the ephemeral status
if ephemeral == 1 {
entry.Ephemeral = true
Expand Down Expand Up @@ -269,23 +286,39 @@ func (c *cmdCopy) copyInstance(conf *config.Config, sourceResource string, destR
entry.Profiles = []string{}
}

// Allow setting additional config keys
for key, value := range configMap {
entry.Config[key] = value
// Check to see if any of the devices overrides are for devices that are not yet defined in the
// local devices and thus are expected to be coming from profiles.
needProfileExpansion := false
for deviceName := range deviceOverrides {
_, isLocalDevice := entry.Devices[deviceName]
if !isLocalDevice {
needProfileExpansion = true
break
}
}

// Allow setting device overrides
for k, m := range deviceMap {
if entry.Devices[k] == nil {
entry.Devices[k] = m
continue
}
profileDevices := make(map[string]map[string]string)

for key, value := range m {
entry.Devices[k][key] = value
// If there are device overrides that are expected to be applied to profile devices then perform
// profile expansion.
if needProfileExpansion {
profileDevices, err = getProfileDevices(dest, entry.Profiles)
if err != nil {
return err
}
}

// Apply device overrides.
entry.Devices, err = shared.ApplyDeviceOverrides(entry.Devices, profileDevices, deviceOverrides)
if err != nil {
return err
}

// Allow setting additional config keys.
for key, value := range configMap {
entry.Config[key] = value
}

// Allow overriding the ephemeral status
if ephemeral == 1 {
entry.Ephemeral = true
Expand Down
43 changes: 7 additions & 36 deletions lxc/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/canonical/lxd/client"
"github.com/canonical/lxd/lxc/config"
"github.com/canonical/lxd/shared"
"github.com/canonical/lxd/shared/api"
cli "github.com/canonical/lxd/shared/cmd"
"github.com/canonical/lxd/shared/i18n"
Expand Down Expand Up @@ -282,46 +283,16 @@ func (c *cmdInit) create(conf *config.Config, args []string) (lxd.InstanceServer
// that would be applied server-side.
if needProfileExpansion {
// If the list of profiles is empty then LXD would apply the default profile on the server side.
serverSideProfiles := req.Profiles
if len(serverSideProfiles) == 0 {
serverSideProfiles = []string{"default"}
}

// Get the effective expanded devices by overlaying each profile's devices in order.
for _, profileName := range serverSideProfiles {
profile, _, err := d.GetProfile(profileName)
if err != nil {
return nil, "", fmt.Errorf(i18n.G("Failed loading profile %q for device override: %w"), profileName, err)
}

for k, v := range profile.Devices {
profileDevices[k] = v
}
profileDevices, err = getProfileDevices(d, req.Profiles)
if err != nil {
return nil, "", err
}
}

// Apply device overrides.
for deviceName := range deviceOverrides {
_, isLocalDevice := devicesMap[deviceName]
if isLocalDevice {
// Apply overrides to local device.
for k, v := range deviceOverrides[deviceName] {
devicesMap[deviceName][k] = v
}
} else {
// Check device exists in expanded profile devices.
profileDeviceConfig, found := profileDevices[deviceName]
if !found {
return nil, "", fmt.Errorf(i18n.G("Cannot override config for device %q: Device not found in profile devices"), deviceName)
}

for k, v := range deviceOverrides[deviceName] {
profileDeviceConfig[k] = v
}

// Add device to local devices.
devicesMap[deviceName] = profileDeviceConfig
}
devicesMap, err = shared.ApplyDeviceOverrides(devicesMap, profileDevices, deviceOverrides)
if err != nil {
return nil, "", err
}

req.Devices = devicesMap
Expand Down
28 changes: 28 additions & 0 deletions lxc/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@ func runBatch(names []string, action func(name string) error) []batchResult {
return results
}

// getProfileDevices retrieves devices from a list of profiles, if the list is empty the default profile is used.
func getProfileDevices(destRemote lxd.InstanceServer, serverSideProfiles []string) (map[string]map[string]string, error) {
var profiles []string

// If the list of profiles is empty then LXD would apply the default profile on the server side.
if len(serverSideProfiles) == 0 {
profiles = []string{"default"}
} else {
profiles = serverSideProfiles
}

profileDevices := make(map[string]map[string]string)

// Get the effective expanded devices by overlaying each profile's devices in order.
for _, profileName := range profiles {
profile, _, err := destRemote.GetProfile(profileName)
if err != nil {
return nil, fmt.Errorf(i18n.G("Failed loading profile %q: %w"), profileName, err)
}

for deviceName, device := range profile.Devices {
profileDevices[deviceName] = device
}
}

return profileDevices, nil
}

// Add a device to an instance.
func instanceDeviceAdd(client lxd.InstanceServer, name string, devName string, dev map[string]string) error {
// Get the instance entry
Expand Down
26 changes: 5 additions & 21 deletions lxd/api_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -791,29 +791,13 @@ func internalImportFromBackup(s *state.State, projectName string, instName strin

// Apply device overrides.
// Do this before calling internalImportRootDevicePopulate so that device overrides are taken into account.
for deviceName := range deviceOverrides {
_, isLocalDevice := backupConf.Container.Devices[deviceName]
if isLocalDevice {
// Apply overrides to local device.
for k, v := range deviceOverrides[deviceName] {
backupConf.Container.Devices[deviceName][k] = v
}
} else {
// Check device exists in expanded profile devices.
profileDeviceConfig, found := backupConf.Container.ExpandedDevices[deviceName]
if !found {
return fmt.Errorf("Cannot override config for device %q: Device not found in profile devices", deviceName)
}

for k, v := range deviceOverrides[deviceName] {
profileDeviceConfig[k] = v
}

// Add device to local devices.
backupConf.Container.Devices[deviceName] = profileDeviceConfig
}
resultingDevices, err := shared.ApplyDeviceOverrides(backupConf.Container.Devices, backupConf.Container.ExpandedDevices, deviceOverrides)
if err != nil {
return err
}

backupConf.Container.Devices = resultingDevices

// Add root device if needed.
// And ensure root device is associated with same pool as instance has been imported to.
internalImportRootDevicePopulate(instancePoolName, backupConf.Container.Devices, backupConf.Container.ExpandedDevices, profiles)
Expand Down
Loading

0 comments on commit c83a912

Please sign in to comment.