Skip to content

Commit 81ab531

Browse files
presztakstgraber
authored andcommitted
incusd: Add support for local live-migration between storage pools
Signed-off-by: Piotr Resztak <[email protected]>
1 parent befc3b3 commit 81ab531

File tree

5 files changed

+76
-13
lines changed

5 files changed

+76
-13
lines changed

cmd/incusd/instance_post.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
dbCluster "github.com/lxc/incus/v6/internal/server/db/cluster"
2020
"github.com/lxc/incus/v6/internal/server/db/operationtype"
2121
"github.com/lxc/incus/v6/internal/server/instance"
22+
"github.com/lxc/incus/v6/internal/server/instance/instancetype"
2223
"github.com/lxc/incus/v6/internal/server/operations"
2324
"github.com/lxc/incus/v6/internal/server/project"
2425
"github.com/lxc/incus/v6/internal/server/request"
@@ -234,9 +235,15 @@ func instancePost(d *Daemon, r *http.Request) response.Response {
234235
return response.BadRequest(fmt.Errorf("Instance must be stopped to be moved statelessly"))
235236
}
236237

237-
// Storage pool changes require a stopped instance.
238+
// Storage pool changes require a target flag.
238239
if req.Pool != "" {
239-
return response.BadRequest(fmt.Errorf("Instance must be stopped to be moved across storage pools"))
240+
if inst.Type() != instancetype.VM {
241+
return response.BadRequest(fmt.Errorf("Storage pool change supported only by virtual-machines"))
242+
}
243+
244+
if target == "" {
245+
return response.BadRequest(fmt.Errorf("Storage pool can be specified only together with target flag"))
246+
}
240247
}
241248

242249
// Project changes require a stopped instance.
@@ -433,7 +440,7 @@ func instancePost(d *Daemon, r *http.Request) response.Response {
433440
}
434441

435442
// Cross-server instance migration.
436-
ws, err := newMigrationSource(inst, req.Live, req.InstanceOnly, req.AllowInconsistent, "", req.Target)
443+
ws, err := newMigrationSource(inst, req.Live, req.InstanceOnly, req.AllowInconsistent, "", "", req.Target)
437444
if err != nil {
438445
return response.InternalError(err)
439446
}
@@ -476,6 +483,11 @@ func migrateInstance(ctx context.Context, s *state.State, inst instance.Instance
476483
return fmt.Errorf("Failed loading instance storage pool: %w", err)
477484
}
478485

486+
// Check that we're not requested to move to the same storage pool we're currently use.
487+
if req.Pool != "" && req.Pool == sourcePool.Name() {
488+
return fmt.Errorf("Requested storage pool is the same as current pool")
489+
}
490+
479491
// Get the DB volume type for the instance.
480492
volType, err := storagePools.InstanceTypeToVolumeType(inst.Type())
481493
if err != nil {
@@ -593,8 +605,8 @@ func migrateInstance(ctx context.Context, s *state.State, inst instance.Instance
593605
req.Name = ""
594606
}
595607

596-
// Handle pool and project moves.
597-
if req.Project != "" || req.Pool != "" {
608+
// Handle pool and project moves for stopped instances.
609+
if (req.Project != "" || req.Pool != "") && !req.Live {
598610
// Get a local client.
599611
args := &incus.ConnectionArgs{
600612
SkipGetServer: true,
@@ -756,7 +768,7 @@ func migrateInstance(ctx context.Context, s *state.State, inst instance.Instance
756768
req.Project = ""
757769
}
758770

759-
// Handle remote migrations (location changes).
771+
// Handle remote migrations (location and storage pool changes).
760772
if targetMemberInfo != nil && inst.Location() != targetMemberInfo.Name {
761773
// Get the client.
762774
networkCert := s.Endpoints.NetworkCert()
@@ -794,7 +806,7 @@ func migrateInstance(ctx context.Context, s *state.State, inst instance.Instance
794806
}
795807

796808
// Setup a new migration source.
797-
sourceMigration, err := newMigrationSource(inst, req.Live, false, req.AllowInconsistent, inst.Name(), nil)
809+
sourceMigration, err := newMigrationSource(inst, req.Live, false, req.AllowInconsistent, inst.Name(), req.Pool, nil)
798810
if err != nil {
799811
return fmt.Errorf("Failed setting up instance migration on source: %w", err)
800812
}
@@ -918,8 +930,9 @@ func migrateInstance(ctx context.Context, s *state.State, inst instance.Instance
918930
return err
919931
}
920932

921-
// Cleanup instance paths on source member if using remote shared storage.
922-
if sourcePool.Driver().Info().Remote {
933+
// Cleanup instance paths on source member if using remote shared storage
934+
// and there was no storage pool change.
935+
if sourcePool.Driver().Info().Remote && req.Pool == "" {
923936
err = sourcePool.CleanupInstancePaths(inst, nil)
924937
if err != nil {
925938
return fmt.Errorf("Failed cleaning up instance paths on source member: %w", err)

cmd/incusd/instance_snapshot.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ func snapshotPost(s *state.State, r *http.Request, snapInst instance.Instance) r
676676
}
677677
}
678678

679-
ws, err := newMigrationSource(snapInst, reqNew.Live, true, false, "", req.Target)
679+
ws, err := newMigrationSource(snapInst, reqNew.Live, true, false, "", "", req.Target)
680680
if err != nil {
681681
return response.SmartError(err)
682682
}

cmd/incusd/instances_post.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"io"
89
"net/http"
@@ -363,6 +364,7 @@ func createFromMigration(ctx context.Context, s *state.State, r *http.Request, p
363364
ClusterMoveSourceName: clusterMoveSourceName,
364365
Refresh: req.Source.Refresh,
365366
RefreshExcludeOlder: req.Source.RefreshExcludeOlder,
367+
StoragePool: storagePool,
366368
}
367369

368370
sink, err := newMigrationSink(&migrationArgs)
@@ -388,6 +390,48 @@ func createFromMigration(ctx context.Context, s *state.State, r *http.Request, p
388390
}
389391

390392
instOp.Done(nil) // Complete operation that was created earlier, to release lock.
393+
394+
// Update root device for instance.
395+
err = s.DB.Cluster.Transaction(context.Background(), func(ctx context.Context, tx *db.ClusterTx) error {
396+
devs := inst.LocalDevices().CloneNative()
397+
rootDevKey, _, err := internalInstance.GetRootDiskDevice(devs)
398+
if err != nil {
399+
if !errors.Is(err, internalInstance.ErrNoRootDisk) {
400+
return err
401+
}
402+
403+
rootDev := map[string]string{}
404+
rootDev["type"] = "disk"
405+
rootDev["path"] = "/"
406+
rootDev["pool"] = storagePool
407+
408+
devs["root"] = rootDev
409+
} else {
410+
// Apply the override.
411+
devs[rootDevKey]["pool"] = storagePool
412+
}
413+
414+
devices, err := dbCluster.APIToDevices(devs)
415+
if err != nil {
416+
return err
417+
}
418+
419+
id, err := dbCluster.GetInstanceID(ctx, tx.Tx(), inst.Project().Name, inst.Name())
420+
if err != nil {
421+
return fmt.Errorf("Failed to get ID of moved instance: %w", err)
422+
}
423+
424+
err = dbCluster.UpdateInstanceDevices(ctx, tx.Tx(), int64(id), devices)
425+
if err != nil {
426+
return err
427+
}
428+
429+
return nil
430+
})
431+
if err != nil {
432+
return err
433+
}
434+
391435
runRevert.Success()
392436

393437
return instanceCreateFinish(s, req, args, op)

cmd/incusd/migrate.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type migrationFields struct {
3737
// storage specific fields
3838
volumeOnly bool
3939
allowInconsistent bool
40+
storagePool string
4041
}
4142

4243
func (c *migrationFields) send(m proto.Message) error {
@@ -207,8 +208,9 @@ type migrationSinkArgs struct {
207208
Snapshots []*migration.Snapshot
208209

209210
// Storage specific fields
210-
VolumeOnly bool
211-
VolumeSize int64
211+
StoragePool string
212+
VolumeOnly bool
213+
VolumeSize int64
212214

213215
// Transport specific fields
214216
RsyncFeatures []string

cmd/incusd/migrate_instance.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import (
2020
"github.com/lxc/incus/v6/shared/logger"
2121
)
2222

23-
func newMigrationSource(inst instance.Instance, stateful bool, instanceOnly bool, allowInconsistent bool, clusterMoveSourceName string, pushTarget *api.InstancePostTarget) (*migrationSourceWs, error) {
23+
func newMigrationSource(inst instance.Instance, stateful bool, instanceOnly bool, allowInconsistent bool, clusterMoveSourceName string, storagePool string, pushTarget *api.InstancePostTarget) (*migrationSourceWs, error) {
2424
ret := migrationSourceWs{
2525
migrationFields: migrationFields{
2626
instance: inst,
2727
allowInconsistent: allowInconsistent,
28+
storagePool: storagePool,
2829
},
2930
clusterMoveSourceName: clusterMoveSourceName,
3031
}
@@ -144,6 +145,7 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
144145
}
145146
},
146147
ClusterMoveSourceName: s.clusterMoveSourceName,
148+
StoragePool: s.storagePool,
147149
},
148150
AllowInconsistent: s.allowInconsistent,
149151
})
@@ -164,6 +166,7 @@ func newMigrationSink(args *migrationSinkArgs) (*migrationSink, error) {
164166
instance: args.Instance,
165167
instanceOnly: args.InstanceOnly,
166168
live: args.Live,
169+
storagePool: args.StoragePool,
167170
},
168171
url: args.URL,
169172
clusterMoveSourceName: args.ClusterMoveSourceName,
@@ -275,6 +278,7 @@ func (c *migrationSink) Do(state *state.State, instOp *operationlock.InstanceOpe
275278
}
276279
},
277280
ClusterMoveSourceName: c.clusterMoveSourceName,
281+
StoragePool: c.storagePool,
278282
},
279283
InstanceOperation: instOp,
280284
Refresh: c.refresh,

0 commit comments

Comments
 (0)