Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions cmd/incusd/patches.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"
"time"

internalInstance "github.com/lxc/incus/v6/internal/instance"
"github.com/lxc/incus/v6/internal/server/backup"
"github.com/lxc/incus/v6/internal/server/certificate"
"github.com/lxc/incus/v6/internal/server/cluster"
Expand Down Expand Up @@ -95,6 +96,7 @@ var patches = []patch{
{name: "network_ovn_directional_port_groups", stage: patchPostDaemonStorage, run: patchGenericNetwork(patchNetworkOVNPortGroups)},
{name: "pool_fix_default_permissions", stage: patchPostDaemonStorage, run: patchDefaultStoragePermissions},
{name: "auth_openfga_volume_files", stage: patchPostNetworks, run: patchGenericAuthorization},
{name: "btrfs_config_volume_subvolume_names", stage: patchPostNetworks, run: patchBtrfsSubvolumeNames},
}

type patchRun func(name string, d *Daemon) error
Expand Down Expand Up @@ -1650,4 +1652,105 @@ func patchDefaultStoragePermissions(_ string, d *Daemon) error {
return nil
}

// patchBtrfsSubvolumeNames updates Btrfs subvolume names for instance config volumes,
// replacing the pattern '<instance-name>-' with 'instance-'.
func patchBtrfsSubvolumeNames(_ string, d *Daemon) error {
s := d.State()

// Get the list of local instances.
instances, err := instance.LoadNodeAll(s, instancetype.Any)
if err != nil {
return fmt.Errorf("Failed loading local instances: %w", err)
}

for _, inst := range instances {
volType, err := storagePools.InstanceTypeToVolumeType(inst.Type())
if err != nil {
return err
}

// Get the volume name on storage.
volStorageName := project.Instance(inst.Project().Name, inst.Name())
contentType := storagePools.InstanceContentType(inst)

_, currentPool, _ := internalInstance.GetRootDiskDevice(inst.ExpandedDevices().CloneNative())

// Load storage pool.
p, err := storagePools.LoadByName(s, currentPool["pool"])
if err != nil {
logger.Error("Failed loading pool", logger.Ctx{"pool": currentPool["pool"], "err": err})
continue
}

// Check if driver is 'lvmcluster'.
if p.Driver().Info().Name != "lvmcluster" {
continue
}

// Load storage volume from database.
dbVol, err := storagePools.VolumeDBGet(p, inst.Project().Name, inst.Name(), volType)
if err != nil {
logger.Error("Failed loading volume", logger.Ctx{"vol": inst.Name(), "err": err})
continue
}

// Process only qcow2 instances.
if dbVol.Config["block.type"] != "qcow2" {
continue
}

vol := p.GetVolume(volType, contentType, volStorageName, dbVol.Config)

newName := storageDrivers.Qcow2ConfigVolumeBase
fsVol := vol.NewVMBlockFilesystemVolume()

// Increment the ref count for running instances,
// since it is equal zero at this stage and may cause an unintended volume deactivation.
if inst.IsRunning() {
fsVol.MountRefCountIncrement()
}

err = storageDrivers.Qcow2MountConfigTask(fsVol, nil, func(mountPath string) error {
_, volName := project.StorageVolumeParts(fsVol.Name())
entries, err := os.ReadDir(mountPath)
if err != nil {
return err
}

// Iterate through all entries (directories) and rename them.
for _, entry := range entries {
if !entry.IsDir() {
continue
}

oldName := entry.Name()

if oldName == volName || strings.HasPrefix(oldName, volName) {
newName := newName + strings.TrimPrefix(oldName, volName)

oldPath := filepath.Join(mountPath, oldName)
newPath := filepath.Join(mountPath, newName)

err := os.Rename(oldPath, newPath)
if err != nil {
return fmt.Errorf("Failed to rename %q to %q: %w", oldPath, newPath, err)
}
}
}

return nil
})
if err != nil {
logger.Error("Failed to rename btrfs subvolumes", logger.Ctx{"err": err})
continue
}

if inst.IsRunning() {
fsVol.MountRefCountDecrement()
}
}

return nil
}

// Patches end here
8 changes: 0 additions & 8 deletions internal/server/storage/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -8029,14 +8029,6 @@ func (b *backend) qcow2Rename(vol drivers.Volume, newVolName string, projectName
return err
}

if vol.IsVMBlock() {
fsVol := vol.NewVMBlockFilesystemVolume()
err := drivers.Qcow2RenameConfig(fsVol, newVolName, op)
if err != nil {
return err
}
}

return nil
}

Expand Down
74 changes: 16 additions & 58 deletions internal/server/storage/drivers/utils_qcow2.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (

"github.com/lxc/incus/v6/internal/linux"
"github.com/lxc/incus/v6/internal/server/operations"
"github.com/lxc/incus/v6/internal/server/project"
internalUtil "github.com/lxc/incus/v6/internal/util"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/subprocess"
Expand All @@ -25,6 +24,9 @@ const (
BlockVolumeTypeQcow2 = "qcow2"
)

// Qcow2ConfigVolumeBase represents the base component of a Btrfs subvolume name.
const Qcow2ConfigVolumeBase = "instance"

// ImageInfo contains information about a qcow2 image.
type ImageInfo struct {
BackingFilename string `json:"backing-filename"`
Expand Down Expand Up @@ -192,8 +194,7 @@ func Qcow2MountConfigTask(vol Volume, op *operations.Operation, task func(mountP
// Qcow2CreateConfig creates the btrfs config filesystem associated with the QCOW2 block volume.
func Qcow2CreateConfig(vol Volume, op *operations.Operation) error {
err := Qcow2MountConfigTask(vol, op, func(mountPath string) error {
_, volName := project.StorageVolumeParts(vol.Name())
volPath := filepath.Join(mountPath, volName)
volPath := filepath.Join(mountPath, Qcow2ConfigVolumeBase)
// Create the volume itself.
_, err := subprocess.RunCommand("btrfs", "subvolume", "create", volPath)
if err != nil {
Expand All @@ -209,52 +210,12 @@ func Qcow2CreateConfig(vol Volume, op *operations.Operation) error {
return nil
}

// Qcow2RenameConfig renames the btrfs config filesystem associated with the QCOW2 block volume.
func Qcow2RenameConfig(vol Volume, newName string, op *operations.Operation) error {
err := Qcow2MountConfigTask(vol, op, func(mountPath string) error {
_, volName := project.StorageVolumeParts(vol.Name())
entries, err := os.ReadDir(mountPath)
if err != nil {
return err
}

// Iterate through all entries (directories) and rename them.
for _, entry := range entries {
if !entry.IsDir() {
continue
}

oldName := entry.Name()

if oldName == volName || strings.HasPrefix(oldName, volName) {
newName := newName + strings.TrimPrefix(oldName, volName)

oldPath := filepath.Join(mountPath, oldName)
newPath := filepath.Join(mountPath, newName)

err := os.Rename(oldPath, newPath)
if err != nil {
return fmt.Errorf("Failed to rename %q to %q: %w", oldPath, newPath, err)
}
}
}

return nil
})
if err != nil {
return err
}

return nil
}

// Qcow2CreateConfigSnapshot creates the btrfs snapshot of the config filesystem associated with the QCOW2 block volume.
func Qcow2CreateConfigSnapshot(vol Volume, snapVol Volume, op *operations.Operation) error {
err := Qcow2MountConfigTask(vol, op, func(mountPath string) error {
fullParent, snapName, _ := api.GetParentAndSnapshotName(snapVol.Name())
_, parent := project.StorageVolumeParts(fullParent)
dstPath := filepath.Join(mountPath, fmt.Sprintf("%s-%s", parent, snapName))
srcPath := filepath.Join(mountPath, parent)
_, snapName, _ := api.GetParentAndSnapshotName(snapVol.Name())
dstPath := filepath.Join(mountPath, fmt.Sprintf("%s-%s", Qcow2ConfigVolumeBase, snapName))
srcPath := filepath.Join(mountPath, Qcow2ConfigVolumeBase)

_, err := subprocess.RunCommand("btrfs", "subvolume", "snapshot", srcPath, dstPath)
if err != nil {
Expand All @@ -273,17 +234,16 @@ func Qcow2CreateConfigSnapshot(vol Volume, snapVol Volume, op *operations.Operat
// Qcow2RestoreConfigSnapshot restores the btrfs snapshot of the config filesystem associated with the QCOW2 block volume.
func Qcow2RestoreConfigSnapshot(vol Volume, snapVol Volume, op *operations.Operation) error {
err := Qcow2MountConfigTask(vol, op, func(mountPath string) error {
fullParent, snapName, _ := api.GetParentAndSnapshotName(snapVol.Name())
_, parent := project.StorageVolumeParts(fullParent)
snapPath := fmt.Sprintf("%s-%s", parent, snapName)
_, snapName, _ := api.GetParentAndSnapshotName(snapVol.Name())
snapPath := fmt.Sprintf("%s-%s", Qcow2ConfigVolumeBase, snapName)

// Delete the subvolume itself.
_, err := subprocess.RunCommand("btrfs", "subvolume", "delete", filepath.Join(mountPath, parent))
_, err := subprocess.RunCommand("btrfs", "subvolume", "delete", filepath.Join(mountPath, Qcow2ConfigVolumeBase))
if err != nil {
return err
}

_, err = subprocess.RunCommand("btrfs", "subvolume", "snapshot", filepath.Join(mountPath, snapPath), filepath.Join(mountPath, parent))
_, err = subprocess.RunCommand("btrfs", "subvolume", "snapshot", filepath.Join(mountPath, snapPath), filepath.Join(mountPath, Qcow2ConfigVolumeBase))
if err != nil {
return err
}
Expand All @@ -300,10 +260,9 @@ func Qcow2RestoreConfigSnapshot(vol Volume, snapVol Volume, op *operations.Opera
// Qcow2RenameConfigSnapshot renames the btrfs snapshot of the config filesystem associated with the QCOW2 block volume.
func Qcow2RenameConfigSnapshot(vol Volume, snapVol Volume, newName string, op *operations.Operation) error {
err := Qcow2MountConfigTask(vol, op, func(mountPath string) error {
fullParent, snapName, _ := api.GetParentAndSnapshotName(snapVol.Name())
_, parent := project.StorageVolumeParts(fullParent)
oldPath := filepath.Join(mountPath, fmt.Sprintf("%s-%s", parent, snapName))
newPath := filepath.Join(mountPath, fmt.Sprintf("%s-%s", parent, newName))
_, snapName, _ := api.GetParentAndSnapshotName(snapVol.Name())
oldPath := filepath.Join(mountPath, fmt.Sprintf("%s-%s", Qcow2ConfigVolumeBase, snapName))
newPath := filepath.Join(mountPath, fmt.Sprintf("%s-%s", Qcow2ConfigVolumeBase, newName))

err := os.Rename(oldPath, newPath)
if err != nil {
Expand All @@ -322,9 +281,8 @@ func Qcow2RenameConfigSnapshot(vol Volume, snapVol Volume, newName string, op *o
// Qcow2DeleteConfigSnapshot deletes the btrfs snapshot of the config filesystem associated with the QCOW2 block volume.
func Qcow2DeleteConfigSnapshot(vol Volume, snapVol Volume, op *operations.Operation) error {
err := Qcow2MountConfigTask(vol, op, func(mountPath string) error {
fullParent, snapName, _ := api.GetParentAndSnapshotName(snapVol.Name())
_, parent := project.StorageVolumeParts(fullParent)
path := filepath.Join(mountPath, fmt.Sprintf("%s-%s", parent, snapName))
_, snapName, _ := api.GetParentAndSnapshotName(snapVol.Name())
path := filepath.Join(mountPath, fmt.Sprintf("%s-%s", Qcow2ConfigVolumeBase, snapName))

// Delete the subvolume itself.
_, err := subprocess.RunCommand("btrfs", "subvolume", "delete", path)
Expand Down
8 changes: 3 additions & 5 deletions internal/server/storage/drivers/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
internalInstance "github.com/lxc/incus/v6/internal/instance"
"github.com/lxc/incus/v6/internal/server/locking"
"github.com/lxc/incus/v6/internal/server/operations"
"github.com/lxc/incus/v6/internal/server/project"
"github.com/lxc/incus/v6/internal/server/refcount"
"github.com/lxc/incus/v6/internal/server/state"
internalUtil "github.com/lxc/incus/v6/internal/util"
Expand Down Expand Up @@ -610,11 +609,10 @@ func (v Volume) ConfigBlockFilesystem() string {
// "block.mount_options" if defined in volume or pool's volume config, otherwise defaultFilesystemMountOptions.
func (v Volume) ConfigBlockMountOptions() string {
if v.ExpandedConfig("block.type") == BlockVolumeTypeQcow2 && !v.mountFullFilesystem {
fullParent, snapName, isSnap := api.GetParentAndSnapshotName(v.name)
_, parent := project.StorageVolumeParts(fullParent)
subvol := parent
_, snapName, isSnap := api.GetParentAndSnapshotName(v.name)
subvol := Qcow2ConfigVolumeBase
if isSnap {
subvol = fmt.Sprintf("%s-%s", parent, snapName)
subvol = fmt.Sprintf("%s-%s", Qcow2ConfigVolumeBase, snapName)
}

return fmt.Sprintf("subvol=%s", subvol)
Expand Down
Loading