diff --git a/cmd/incusd/migrate_storage_volumes.go b/cmd/incusd/migrate_storage_volumes.go index 3d37538c6b4..7fc2906fe27 100644 --- a/cmd/incusd/migrate_storage_volumes.go +++ b/cmd/incusd/migrate_storage_volumes.go @@ -138,7 +138,8 @@ func (s *migrationSourceWs) DoStorage(state *state.State, projectName string, po // to false here. The migration source/sender doesn't need to care whether // or not it's doing a refresh as the migration sink/receiver will know // this, and adjust the migration types accordingly. - poolMigrationTypes = pool.MigrationTypes(storageDrivers.ContentType(srcConfig.Volume.ContentType), false, !s.volumeOnly) + // The same applies for clusterMove and storageMove, which are set to the most optimized defaults. + poolMigrationTypes = pool.MigrationTypes(storageDrivers.ContentType(srcConfig.Volume.ContentType), false, !s.volumeOnly, true, false) if len(poolMigrationTypes) == 0 { return fmt.Errorf("No source migration types available") } @@ -338,10 +339,12 @@ func (c *migrationSink) DoStorage(state *state.State, projectName string, poolNa // Refresh needs to be set. offerHeader.Refresh = &c.refresh + clusterMove := req.Source.Location != "" + // Extract the source's migration type and then match it against our pool's // supported types and features. If a match is found the combined features list // will be sent back to requester. - respTypes, err := localMigration.MatchTypes(offerHeader, storagePools.FallbackMigrationType(contentType), pool.MigrationTypes(contentType, c.refresh, !c.volumeOnly)) + respTypes, err := localMigration.MatchTypes(offerHeader, storagePools.FallbackMigrationType(contentType), pool.MigrationTypes(contentType, c.refresh, !c.volumeOnly, clusterMove, poolName != "" && req.Source.Pool != poolName || !clusterMove)) if err != nil { return err } diff --git a/internal/server/instance/drivers/driver_lxc.go b/internal/server/instance/drivers/driver_lxc.go index 41b51315827..63b177ce985 100644 --- a/internal/server/instance/drivers/driver_lxc.go +++ b/internal/server/instance/drivers/driver_lxc.go @@ -5502,10 +5502,14 @@ func (d *lxc) MigrateSend(args instance.MigrateSendArgs) error { return err } + clusterMove := args.ClusterMoveSourceName != "" + storageMove := args.StoragePool != "" + // The refresh argument passed to MigrationTypes() is always set to false here. // The migration source/sender doesn't need to care whether or not it's doing a refresh as the migration // sink/receiver will know this, and adjust the migration types accordingly. - poolMigrationTypes := pool.MigrationTypes(storagePools.InstanceContentType(d), false, args.Snapshots) + // The same applies for clusterMove and storageMove, which are set to the most optimized defaults. + poolMigrationTypes := pool.MigrationTypes(storagePools.InstanceContentType(d), false, args.Snapshots, true, false) if len(poolMigrationTypes) == 0 { err := fmt.Errorf("No source migration types available") op.Done(err) @@ -5614,7 +5618,8 @@ func (d *lxc) MigrateSend(args instance.MigrateSendArgs) error { AllowInconsistent: args.AllowInconsistent, VolumeOnly: !args.Snapshots, Info: &localMigration.Info{Config: srcConfig}, - ClusterMove: args.ClusterMoveSourceName != "", + ClusterMove: clusterMove, + StorageMove: storageMove, } // Only send the snapshots that the target requests when refreshing. @@ -6151,10 +6156,13 @@ func (d *lxc) MigrateReceive(args instance.MigrateReceiveArgs) error { // However, to determine the correct migration type Refresh needs to be set. offerHeader.Refresh = &args.Refresh + clusterMove := args.ClusterMoveSourceName != "" + storageMove := args.StoragePool != "" + // Extract the source's migration type and then match it against our pool's supported types and features. // If a match is found the combined features list will be sent back to requester. contentType := storagePools.InstanceContentType(d) - respTypes, err := localMigration.MatchTypes(offerHeader, storagePools.FallbackMigrationType(contentType), pool.MigrationTypes(contentType, args.Refresh, args.Snapshots)) + respTypes, err := localMigration.MatchTypes(offerHeader, storagePools.FallbackMigrationType(contentType), pool.MigrationTypes(contentType, args.Refresh, args.Snapshots, clusterMove, storageMove)) if err != nil { return err } @@ -6378,6 +6386,7 @@ func (d *lxc) MigrateReceive(args instance.MigrateReceiveArgs) error { VolumeSize: offerHeader.GetVolumeSize(), // Block size setting override. VolumeOnly: !args.Snapshots, ClusterMoveSourceName: args.ClusterMoveSourceName, + StoragePool: args.StoragePool, } // At this point we have already figured out the parent container's root @@ -6436,7 +6445,7 @@ func (d *lxc) MigrateReceive(args instance.MigrateReceiveArgs) error { return fmt.Errorf("Failed creating instance on target: %w", err) } - isRemoteClusterMove := args.ClusterMoveSourceName != "" && pool.Driver().Info().Remote + isRemoteClusterMove := clusterMove && pool.Driver().Info().Remote // Only delete all instance volumes on error if the pool volume creation has succeeded to // avoid deleting an existing conflicting volume. diff --git a/internal/server/instance/drivers/driver_qemu.go b/internal/server/instance/drivers/driver_qemu.go index 63335e6b69c..7251740061e 100644 --- a/internal/server/instance/drivers/driver_qemu.go +++ b/internal/server/instance/drivers/driver_qemu.go @@ -6702,11 +6702,15 @@ func (d *qemu) MigrateSend(args instance.MigrateSendArgs) error { return err } + clusterMove := args.ClusterMoveSourceName != "" + storageMove := args.StoragePool != "" + // The refresh argument passed to MigrationTypes() is always set // to false here. The migration source/sender doesn't need to care whether // or not it's doing a refresh as the migration sink/receiver will know // this, and adjust the migration types accordingly. - poolMigrationTypes := pool.MigrationTypes(storagePools.InstanceContentType(d), false, args.Snapshots) + // The same applies for clusterMove and storageMove, which are set to the most optimized defaults. + poolMigrationTypes := pool.MigrationTypes(storagePools.InstanceContentType(d), false, args.Snapshots, true, false) if len(poolMigrationTypes) == 0 { err := fmt.Errorf("No source migration types available") op.Done(err) @@ -6804,8 +6808,8 @@ func (d *qemu) MigrateSend(args instance.MigrateSendArgs) error { AllowInconsistent: args.AllowInconsistent, VolumeOnly: !args.Snapshots, Info: &localMigration.Info{Config: srcConfig}, - ClusterMove: args.ClusterMoveSourceName != "", - StorageMove: args.StoragePool != "", + ClusterMove: clusterMove, + StorageMove: storageMove, } // Only send the snapshots that the target requests when refreshing. @@ -7345,10 +7349,13 @@ func (d *qemu) MigrateReceive(args instance.MigrateReceiveArgs) error { // However, to determine the correct migration type Refresh needs to be set. offerHeader.Refresh = &args.Refresh + clusterMove := args.ClusterMoveSourceName != "" + storageMove := args.StoragePool != "" + // Extract the source's migration type and then match it against our pool's supported types and features. // If a match is found the combined features list will be sent back to requester. contentType := storagePools.InstanceContentType(d) - respTypes, err := localMigration.MatchTypes(offerHeader, storagePools.FallbackMigrationType(contentType), pool.MigrationTypes(contentType, args.Refresh, args.Snapshots)) + respTypes, err := localMigration.MatchTypes(offerHeader, storagePools.FallbackMigrationType(contentType), pool.MigrationTypes(contentType, args.Refresh, args.Snapshots, clusterMove, storageMove)) if err != nil { return err } @@ -7657,7 +7664,7 @@ func (d *qemu) MigrateReceive(args instance.MigrateReceiveArgs) error { // Only delete all instance volumes on error if the pool volume creation has succeeded to // avoid deleting an existing conflicting volume. - isRemoteClusterMove := args.ClusterMoveSourceName != "" && poolInfo.Remote + isRemoteClusterMove := clusterMove && poolInfo.Remote if !volTargetArgs.Refresh && !isRemoteClusterMove { revert.Add(func() { snapshots, _ := d.Snapshots() diff --git a/internal/server/storage/backend.go b/internal/server/storage/backend.go index 4985ac43536..42b91956b8b 100644 --- a/internal/server/storage/backend.go +++ b/internal/server/storage/backend.go @@ -165,12 +165,13 @@ func (b *backend) Driver() drivers.Driver { return b.driver } -// MigrationTypes returns the migration transport method preferred when sending a migration, -// based on the migration method requested by the driver's ability. The snapshots argument -// indicates whether snapshots are migrated as well. It is used to determine whether to use -// optimized migration. -func (b *backend) MigrationTypes(contentType drivers.ContentType, refresh bool, copySnapshots bool) []localMigration.Type { - return b.driver.MigrationTypes(contentType, refresh, copySnapshots) +// MigrationTypes returns the migration transport method preferred when sending a migration, based +// on the migration method requested by the driver's ability. The copySnapshots argument indicates +// whether snapshots are migrated as well. clusterMove determines whether the migration is done +// within a cluster and storageMove determines whether the storage pool is changed by the migration. +// This method is used to determine whether to use optimized migration. +func (b *backend) MigrationTypes(contentType drivers.ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []localMigration.Type { + return b.driver.MigrationTypes(contentType, refresh, copySnapshots, clusterMove, storageMove) } // Create creates the storage pool layout on the storage device. @@ -1150,9 +1151,9 @@ func (b *backend) CreateInstanceFromCopy(inst instance.Instance, src instance.In l.Debug("CreateInstanceFromCopy cross-pool mode detected") // Negotiate the migration type to use. - offeredTypes := srcPool.MigrationTypes(contentType, false, snapshots) + offeredTypes := srcPool.MigrationTypes(contentType, false, snapshots, false, true) offerHeader := localMigration.TypesToHeader(offeredTypes...) - migrationTypes, err := localMigration.MatchTypes(offerHeader, FallbackMigrationType(contentType), b.MigrationTypes(contentType, false, snapshots)) + migrationTypes, err := localMigration.MatchTypes(offerHeader, FallbackMigrationType(contentType), b.MigrationTypes(contentType, false, snapshots, false, true)) if err != nil { return fmt.Errorf("Failed to negotiate copy migration type: %w", err) } @@ -1196,6 +1197,7 @@ func (b *backend) CreateInstanceFromCopy(inst instance.Instance, src instance.In AllowInconsistent: allowInconsistent, VolumeOnly: !snapshots, Info: &localMigration.Info{Config: srcConfig}, + StorageMove: true, }, op) }) @@ -1208,6 +1210,7 @@ func (b *backend) CreateInstanceFromCopy(inst instance.Instance, src instance.In VolumeSize: srcVolumeSize, // Block size setting override. TrackProgress: false, // Do not use a progress tracker on receiver. VolumeOnly: !snapshots, + StoragePool: srcPool.Name(), }, op) }) @@ -1398,9 +1401,9 @@ func (b *backend) RefreshCustomVolume(projectName string, srcProjectName string, l.Debug("RefreshCustomVolume cross-pool mode detected") // Negotiate the migration type to use. - offeredTypes := srcPool.MigrationTypes(contentType, true, snapshots) + offeredTypes := srcPool.MigrationTypes(contentType, true, snapshots, false, true) offerHeader := localMigration.TypesToHeader(offeredTypes...) - migrationTypes, err := localMigration.MatchTypes(offerHeader, FallbackMigrationType(contentType), b.MigrationTypes(contentType, true, snapshots)) + migrationTypes, err := localMigration.MatchTypes(offerHeader, FallbackMigrationType(contentType), b.MigrationTypes(contentType, true, snapshots, false, true)) if err != nil { return fmt.Errorf("Failed to negotiate copy migration type: %w", err) } @@ -1456,6 +1459,7 @@ func (b *backend) RefreshCustomVolume(projectName string, srcProjectName string, TrackProgress: true, // Do use a progress tracker on sender. ContentType: string(contentType), Info: &localMigration.Info{Config: srcConfig}, + StorageMove: true, }, op) if err != nil { cancel() @@ -1476,6 +1480,7 @@ func (b *backend) RefreshCustomVolume(projectName string, srcProjectName string, ContentType: string(contentType), VolumeSize: volSize, // Block size setting override. Refresh: true, + StoragePool: srcPoolName, }, op) if err != nil { cancel() @@ -1643,9 +1648,9 @@ func (b *backend) RefreshInstance(inst instance.Instance, src instance.Instance, l.Debug("RefreshInstance cross-pool mode detected") // Negotiate the migration type to use. - offeredTypes := srcPool.MigrationTypes(contentType, true, snapshots) + offeredTypes := srcPool.MigrationTypes(contentType, true, snapshots, false, true) offerHeader := localMigration.TypesToHeader(offeredTypes...) - migrationTypes, err := localMigration.MatchTypes(offerHeader, FallbackMigrationType(contentType), b.MigrationTypes(contentType, true, snapshots)) + migrationTypes, err := localMigration.MatchTypes(offerHeader, FallbackMigrationType(contentType), b.MigrationTypes(contentType, true, snapshots, false, true)) if err != nil { return fmt.Errorf("Failed to negotiate copy migration type: %w", err) } @@ -1686,6 +1691,7 @@ func (b *backend) RefreshInstance(inst instance.Instance, src instance.Instance, Refresh: true, // Indicate to sender to use incremental streams. Info: &localMigration.Info{Config: srcConfig}, VolumeOnly: !snapshots, + StorageMove: true, }, op) }) @@ -1699,6 +1705,7 @@ func (b *backend) RefreshInstance(inst instance.Instance, src instance.Instance, VolumeSize: srcVolumeSize, TrackProgress: false, // Do not use a progress tracker on receiver. VolumeOnly: !snapshots, + StoragePool: srcPool.Name(), }, op) }) @@ -4812,9 +4819,9 @@ func (b *backend) CreateCustomVolumeFromCopy(projectName string, srcProjectName l.Debug("CreateCustomVolumeFromCopy cross-pool mode detected") // Negotiate the migration type to use. - offeredTypes := srcPool.MigrationTypes(contentType, false, snapshots) + offeredTypes := srcPool.MigrationTypes(contentType, false, snapshots, false, true) offerHeader := localMigration.TypesToHeader(offeredTypes...) - migrationTypes, err := localMigration.MatchTypes(offerHeader, FallbackMigrationType(contentType), b.MigrationTypes(contentType, false, snapshots)) + migrationTypes, err := localMigration.MatchTypes(offerHeader, FallbackMigrationType(contentType), b.MigrationTypes(contentType, false, snapshots, false, true)) if err != nil { return fmt.Errorf("Failed to negotiate copy migration type: %w", err) } @@ -4874,6 +4881,7 @@ func (b *backend) CreateCustomVolumeFromCopy(projectName string, srcProjectName ContentType: string(contentType), Info: &localMigration.Info{Config: srcConfig}, VolumeOnly: !snapshots, + StorageMove: true, }, op) if err != nil { cancel() @@ -4894,6 +4902,7 @@ func (b *backend) CreateCustomVolumeFromCopy(projectName string, srcProjectName ContentType: string(contentType), VolumeSize: volSize, // Block size setting override. VolumeOnly: !snapshots, + StoragePool: srcPool.Name(), }, op) if err != nil { cancel() diff --git a/internal/server/storage/backend_mock.go b/internal/server/storage/backend_mock.go index c93f1f4bfb4..28c67972fc8 100644 --- a/internal/server/storage/backend_mock.go +++ b/internal/server/storage/backend_mock.go @@ -63,7 +63,8 @@ func (b *mockBackend) Driver() drivers.Driver { return b.driver } -func (b *mockBackend) MigrationTypes(contentType drivers.ContentType, refresh bool, copySnapshots bool) []migration.Type { +// MigrationTypes returns the type of transfer methods to be used when doing migrations between pools in preference order. +func (b *mockBackend) MigrationTypes(contentType drivers.ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []migration.Type { return []migration.Type{ { FSType: FallbackMigrationType(contentType), diff --git a/internal/server/storage/drivers/driver_btrfs.go b/internal/server/storage/drivers/driver_btrfs.go index cc4c6030bb9..82148c59782 100644 --- a/internal/server/storage/drivers/driver_btrfs.go +++ b/internal/server/storage/drivers/driver_btrfs.go @@ -488,7 +488,7 @@ func (d *btrfs) GetResources() (*api.ResourcesStoragePool, error) { } // MigrationType returns the type of transfer methods to be used when doing migrations between pools in preference order. -func (d *btrfs) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool) []localMigration.Type { +func (d *btrfs) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []localMigration.Type { var rsyncFeatures []string btrfsFeatures := []string{migration.BTRFSFeatureMigrationHeader, migration.BTRFSFeatureSubvolumes, migration.BTRFSFeatureSubvolumeUUIDs} diff --git a/internal/server/storage/drivers/driver_ceph.go b/internal/server/storage/drivers/driver_ceph.go index 5f1f740fea2..3dc84167a44 100644 --- a/internal/server/storage/drivers/driver_ceph.go +++ b/internal/server/storage/drivers/driver_ceph.go @@ -394,7 +394,7 @@ func (d *ceph) GetResources() (*api.ResourcesStoragePool, error) { } // MigrationType returns the type of transfer methods to be used when doing migrations between pools in preference order. -func (d *ceph) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool) []localMigration.Type { +func (d *ceph) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []localMigration.Type { var rsyncFeatures []string // Do not pass compression argument to rsync if the associated diff --git a/internal/server/storage/drivers/driver_cephfs.go b/internal/server/storage/drivers/driver_cephfs.go index 762555324ed..4c77f15c52e 100644 --- a/internal/server/storage/drivers/driver_cephfs.go +++ b/internal/server/storage/drivers/driver_cephfs.go @@ -482,7 +482,7 @@ func (d *cephfs) GetResources() (*api.ResourcesStoragePool, error) { } // MigrationTypes returns the supported migration types and options supported by the driver. -func (d *cephfs) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool) []localMigration.Type { +func (d *cephfs) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []localMigration.Type { var rsyncFeatures []string // Do not pass compression argument to rsync if the associated diff --git a/internal/server/storage/drivers/driver_cephobject.go b/internal/server/storage/drivers/driver_cephobject.go index 9f5665fad9c..d2add44ab31 100644 --- a/internal/server/storage/drivers/driver_cephobject.go +++ b/internal/server/storage/drivers/driver_cephobject.go @@ -201,6 +201,6 @@ func (d *cephobject) GetResources() (*api.ResourcesStoragePool, error) { } // MigrationTypes returns the supported migration types and options supported by the driver. -func (d *cephobject) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool) []migration.Type { +func (d *cephobject) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []migration.Type { return nil } diff --git a/internal/server/storage/drivers/driver_common.go b/internal/server/storage/drivers/driver_common.go index 8b77ddb6b02..362d6f29579 100644 --- a/internal/server/storage/drivers/driver_common.go +++ b/internal/server/storage/drivers/driver_common.go @@ -202,7 +202,7 @@ func (d *common) validateVolume(vol Volume, driverRules map[string]func(value st // MigrationType returns the type of transfer methods to be used when doing migrations between pools // in preference order. -func (d *common) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool) []localMigration.Type { +func (d *common) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []localMigration.Type { var transportType migration.MigrationFSType var rsyncFeatures []string diff --git a/internal/server/storage/drivers/driver_zfs.go b/internal/server/storage/drivers/driver_zfs.go index 1429258b3cb..82e6e7ab2c4 100644 --- a/internal/server/storage/drivers/driver_zfs.go +++ b/internal/server/storage/drivers/driver_zfs.go @@ -672,7 +672,7 @@ func (d *zfs) GetResources() (*api.ResourcesStoragePool, error) { } // MigrationType returns the type of transfer methods to be used when doing migrations between pools in preference order. -func (d *zfs) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool) []localMigration.Type { +func (d *zfs) MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []localMigration.Type { var rsyncFeatures []string // Do not pass compression argument to rsync if the associated diff --git a/internal/server/storage/drivers/interface.go b/internal/server/storage/drivers/interface.go index 178bf29f637..a250ce55fc6 100644 --- a/internal/server/storage/drivers/interface.go +++ b/internal/server/storage/drivers/interface.go @@ -102,7 +102,7 @@ type Driver interface { RestoreVolume(vol Volume, snapshotName string, op *operations.Operation) error // Migration. - MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool) []migration.Type + MigrationTypes(contentType ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []migration.Type MigrateVolume(vol Volume, conn io.ReadWriteCloser, volSrcArgs *migration.VolumeSourceArgs, op *operations.Operation) error CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, volTargetArgs migration.VolumeTargetArgs, preFiller *VolumeFiller, op *operations.Operation) error diff --git a/internal/server/storage/pool_interface.go b/internal/server/storage/pool_interface.go index 7269fae30a2..eb2ff424c61 100644 --- a/internal/server/storage/pool_interface.go +++ b/internal/server/storage/pool_interface.go @@ -138,7 +138,7 @@ type Pool interface { RestoreCustomVolume(projectName string, volName string, snapshotName string, op *operations.Operation) error // Custom volume migration. - MigrationTypes(contentType drivers.ContentType, refresh bool, copySnapshots bool) []migration.Type + MigrationTypes(contentType drivers.ContentType, refresh bool, copySnapshots bool, clusterMove bool, storageMove bool) []migration.Type CreateCustomVolumeFromMigration(projectName string, conn io.ReadWriteCloser, args migration.VolumeTargetArgs, op *operations.Operation) error MigrateCustomVolume(projectName string, conn io.ReadWriteCloser, args *migration.VolumeSourceArgs, op *operations.Operation) error diff --git a/internal/server/sys/os.go b/internal/server/sys/os.go index ab39d1815d5..efd926b341e 100644 --- a/internal/server/sys/os.go +++ b/internal/server/sys/os.go @@ -56,6 +56,7 @@ type OS struct { MockMode bool // If true some APIs will be mocked (for testing) Nodev bool RunningInUserNS bool + Hostname string // Privilege dropping UnprivUser string @@ -172,6 +173,10 @@ func (s *OS) Init() ([]cluster.Warning, error) { s.IdmapSet = getIdmapset() s.ExecPath = localUtil.GetExecPath() s.RunningInUserNS = linux.RunningInUserNS() + s.Hostname, err = os.Hostname() + if err != nil { + return nil, err + } dbWarnings = s.initAppArmor() cgroup.Init() diff --git a/shared/validate/validate.go b/shared/validate/validate.go index 4898a41168d..63f4fbfe94f 100644 --- a/shared/validate/validate.go +++ b/shared/validate/validate.go @@ -11,6 +11,7 @@ import ( "slices" "strconv" "strings" + "time" "github.com/google/uuid" "github.com/kballard/go-shellquote" @@ -882,3 +883,30 @@ func IsValidCPUSet(value string) error { return nil } + +// IsShorterThan checks whether a string is shorter than a specific length. +func IsShorterThan(length int) func(value string) error { + return func(value string) error { + if len(value) > length { + return fmt.Errorf("Value is too long. Must be within %d characters", length) + } + + return nil + } +} + +// IsMinimumDuration validates whether a value is a duration longer than a specific minimum. +func IsMinimumDuration(minimum time.Duration) func(value string) error { + return func(value string) error { + duration, err := time.ParseDuration(value) + if err != nil { + return fmt.Errorf("Invalid duration") + } + + if duration < minimum { + return fmt.Errorf("Duration must be greater than %s", minimum) + } + + return nil + } +} diff --git a/test/suites/basic.sh b/test/suites/basic.sh index fbe465427a5..82b26a77027 100644 --- a/test/suites/basic.sh +++ b/test/suites/basic.sh @@ -452,7 +452,7 @@ test_basic_usage() { # test incus file edit doesn't change target file's owner and permissions echo "content" | incus file push - foo/tmp/edit_test - incus exec foo -- chown 55.55 /tmp/edit_test + incus exec foo -- chown 55:55 /tmp/edit_test incus exec foo -- chmod 555 /tmp/edit_test echo "new content" | incus file edit foo/tmp/edit_test [ "$(incus exec foo -- cat /tmp/edit_test)" = "new content" ] diff --git a/test/suites/database.sh b/test/suites/database.sh index b4011785206..3a06237ceef 100644 --- a/test/suites/database.sh +++ b/test/suites/database.sh @@ -95,6 +95,10 @@ test_database_no_disk_space(){ [ "${succeeded}" = "yes" ] || false incus delete -f c + + # The cleanup routine does not take care of image deletion in this test, + # because the socket is closed and the DB is deleted before cleanup + incus image delete testimage ) shutdown_incus "${INCUS_NOSPACE_DIR}" diff --git a/test/suites/remote.sh b/test/suites/remote.sh index 9007f81fd44..df6d1af9070 100644 --- a/test/suites/remote.sh +++ b/test/suites/remote.sh @@ -361,4 +361,7 @@ test_remote_usage() { incus_remote remote remove incus2-public kill_incus "$INCUS2_DIR" + + # Cleanup the image from the original remote + incus image delete testimage }