Skip to content

Commit 0f3f457

Browse files
committed
on demand for clone and restore
1 parent 3b68c4d commit 0f3f457

6 files changed

Lines changed: 35 additions & 6 deletions

File tree

cmd/core/helpers.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ func CloneVMConfigFromFlags(cmd *cobra.Command, snapCfg *types.SnapshotConfig) (
318318
return nil, err
319319
}
320320

321+
onDemand, _ := cmd.Flags().GetBool("on-demand")
322+
321323
cfg := &types.VMConfig{
322324
Name: vmName,
323325
CPU: cpu,
@@ -329,6 +331,7 @@ func CloneVMConfigFromFlags(cmd *cobra.Command, snapCfg *types.SnapshotConfig) (
329331
Network: network,
330332
NoDirectIO: noDirectIO,
331333
Windows: snapCfg.Windows,
334+
OnDemand: onDemand,
332335
}
333336
if err := cfg.Validate(); err != nil {
334337
return nil, err
@@ -350,6 +353,9 @@ func RestoreVMConfigFromFlags(cmd *cobra.Command, vm *types.VM, snapCfg *types.S
350353
result.Memory = memBytes
351354
result.Storage = storBytes
352355

356+
onDemand, _ := cmd.Flags().GetBool("on-demand")
357+
result.OnDemand = onDemand
358+
353359
return &result, nil
354360
}
355361

cmd/vm/commands.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ func Command(h Actions) *cobra.Command {
109109
restoreCmd.Flags().Int("cpu", 0, "boot CPUs (0 = keep current)")
110110
restoreCmd.Flags().String("memory", "", "memory size (empty = keep current)")
111111
restoreCmd.Flags().String("storage", "", "COW disk size (empty = keep current)")
112+
restoreCmd.Flags().Bool("on-demand", false, "use UFFD on-demand memory loading for faster restore (CH only; snapshot file must remain on disk)")
112113

113114
debugCmd := &cobra.Command{
114115
Use: "debug [flags] IMAGE",
@@ -176,4 +177,5 @@ func addCloneFlags(cmd *cobra.Command) {
176177
cmd.Flags().String("network", "", "CNI conflist name (empty = inherit from source VM)")
177178
cmd.Flags().String("bridge", "", "use TAP-on-bridge instead of CNI (value is bridge device, e.g. cni0)")
178179
cmd.Flags().Bool("no-direct-io", false, "disable O_DIRECT on writable disks (inherit from snapshot if not set)")
180+
cmd.Flags().Bool("on-demand", false, "use UFFD on-demand memory loading for faster clone (CH only; snapshot file must remain on disk)")
179181
}

hypervisor/cloudhypervisor/clone.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ func (ch *CloudHypervisor) cloneAfterExtract(ctx context.Context, vmID string, v
134134
cpu: vmCfg.CPU,
135135
diskQueueSize: vmCfg.DiskQueueSize,
136136
noDirectIO: vmCfg.NoDirectIO,
137+
onDemand: vmCfg.OnDemand,
137138
}); err != nil {
138139
return nil, err
139140
}
@@ -162,6 +163,7 @@ type cloneResumeOpts struct {
162163
cpu int
163164
diskQueueSize int
164165
noDirectIO bool
166+
onDemand bool
165167
}
166168

167169
func (ch *CloudHypervisor) restoreAndResumeClone(
@@ -176,7 +178,7 @@ func (ch *CloudHypervisor) restoreAndResumeClone(
176178
}
177179
}()
178180

179-
if err = restoreVM(ctx, sockPath, runDir); err != nil {
181+
if err = restoreVM(ctx, sockPath, runDir, opts.onDemand); err != nil {
180182
return fmt.Errorf("vm.restore: %w", err)
181183
}
182184
hc := utils.NewSocketHTTPClient(sockPath)

hypervisor/cloudhypervisor/helper.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,28 @@ func snapshotVM(ctx context.Context, sockPath, destDir string) error {
8282
return err
8383
}
8484

85-
func restoreVM(ctx context.Context, sockPath, sourceDir string) error {
86-
body, err := json.Marshal(map[string]string{
87-
"source_url": "file://" + sourceDir,
88-
})
85+
// chMemoryRestoreMode controls how CH restores guest memory from a snapshot.
86+
type chMemoryRestoreMode string
87+
88+
const (
89+
// chMemoryRestoreOnDemand uses userfaultfd (UFFD) to lazily page in
90+
// guest memory from the snapshot file, avoiding a full upfront copy.
91+
chMemoryRestoreOnDemand chMemoryRestoreMode = "OnDemand"
92+
)
93+
94+
type chRestoreConfig struct {
95+
SourceURL string `json:"source_url"`
96+
MemoryRestoreMode chMemoryRestoreMode `json:"memory_restore_mode,omitempty"`
97+
}
98+
99+
func restoreVM(ctx context.Context, sockPath, sourceDir string, onDemand bool) error {
100+
cfg := chRestoreConfig{
101+
SourceURL: "file://" + sourceDir,
102+
}
103+
if onDemand {
104+
cfg.MemoryRestoreMode = chMemoryRestoreOnDemand
105+
}
106+
body, err := json.Marshal(cfg)
89107
if err != nil {
90108
return fmt.Errorf("marshal restore request: %w", err)
91109
}

hypervisor/cloudhypervisor/restore.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func (ch *CloudHypervisor) restoreAfterExtract(ctx context.Context, vmID string,
135135
}
136136
}()
137137

138-
if err = restoreVM(ctx, sockPath, rec.RunDir); err != nil {
138+
if err = restoreVM(ctx, sockPath, rec.RunDir, vmCfg.OnDemand); err != nil {
139139
return nil, fmt.Errorf("vm.restore: %w", err)
140140
}
141141
hc := utils.NewSocketHTTPClient(sockPath)

types/vm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type VMConfig struct {
3838

3939
NoDirectIO bool `json:"no_direct_io,omitempty"` // disable O_DIRECT on writable disks
4040
Windows bool `json:"windows,omitempty"` // Windows guest: UEFI boot, kvm_hyperv=on, no cidata
41+
OnDemand bool `json:"-"` // use UFFD on-demand memory restore (CH only); transient, not persisted
4142

4243
// Transient cloud-init credentials — carried in-memory from CLI to cidata
4344
// generation, never serialized to JSON or persisted in the VM record.

0 commit comments

Comments
 (0)