Skip to content

Commit a910ff5

Browse files
committed
runtime: Refactor factory/hypervisor save/restore paths for clearer separation
wip commit message
1 parent 2fa1e7e commit a910ff5

22 files changed

Lines changed: 359 additions & 346 deletions

src/runtime/virtcontainers/clh.go

Lines changed: 161 additions & 163 deletions
Large diffs are not rendered by default.

src/runtime/virtcontainers/clh_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -596,8 +596,6 @@ func TestClhSaveVM(t *testing.T) {
596596

597597
clhConfig, err := newClhConfig()
598598
assert.NoError(err)
599-
// For testing, assume the memory path is located within the VM store path.
600-
clhConfig.MemoryPath = filepath.Join(store.RunVMStoragePath(), "memory")
601599
clhConfig.VMStorePath = store.RunVMStoragePath()
602600
clhConfig.RunStorePath = store.RunStoragePath()
603601

@@ -607,11 +605,12 @@ func TestClhSaveVM(t *testing.T) {
607605
APIClient: mockClient,
608606
}
609607

610-
err = clh.SaveVM()
608+
snapshotDir := store.RunVMStoragePath()
609+
err = clh.SaveVM(snapshotDir)
611610
assert.NoError(err)
612611

613612
if assert.NotNil(mockClient.snapshotRequest) {
614-
expectedDestinationURL := "file://" + filepath.Dir(clhConfig.MemoryPath)
613+
expectedDestinationURL := "file://" + snapshotDir
615614
assert.Equal(expectedDestinationURL, mockClient.snapshotRequest.GetDestinationUrl())
616615
}
617616
}
@@ -670,7 +669,7 @@ func TestCloudHypervisorStartSandbox(t *testing.T) {
670669
err = clh.PauseVM(context.Background())
671670
assert.NoError(err)
672671

673-
err = clh.SaveVM()
672+
err = clh.SaveVM(clhConfig.VMStorePath)
674673
assert.NoError(err)
675674

676675
err = clh.ResumeVM(context.Background())

src/runtime/virtcontainers/factory/factory_linux.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,7 @@ func NewFactory(ctx context.Context, config Config, fetchOnly bool) (vc.Factory,
7373
func resetHypervisorConfig(config *vc.VMConfig) {
7474
config.HypervisorConfig.NumVCPUsF = 0
7575
config.HypervisorConfig.MemorySize = 0
76-
config.HypervisorConfig.BootToBeTemplate = false
77-
config.HypervisorConfig.BootFromTemplate = false
78-
config.HypervisorConfig.MemoryPath = ""
79-
config.HypervisorConfig.DevicesStatePath = ""
76+
config.HypervisorConfig.FileBackedMemory = nil
8077
config.HypervisorConfig.SharedPath = ""
8178
config.HypervisorConfig.VMStorePath = ""
8279
config.HypervisorConfig.RunStorePath = ""

src/runtime/virtcontainers/factory/template/template_linux.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,13 @@ func (t *template) prepareTemplateFiles() error {
131131
func (t *template) createTemplateVM(ctx context.Context) error {
132132
// create the template vm
133133
config := t.config
134-
config.HypervisorConfig.BootToBeTemplate = true
135-
config.HypervisorConfig.BootFromTemplate = false
136-
config.HypervisorConfig.MemoryPath = t.statePath + "/memory"
137-
config.HypervisorConfig.DevicesStatePath = t.deviceStatePath()
134+
// The template source VM is backed by a shared memory file so that clones
135+
// can map the same file. The factory expresses this through the generic
136+
// file-backed memory config rather than template-specific flags.
137+
config.HypervisorConfig.FileBackedMemory = &vc.FileBackedMemoryConfig{
138+
Path: t.statePath + "/memory",
139+
Shared: true,
140+
}
138141
config.HypervisorConfig.VMStorePath = t.statePath
139142

140143
vm, err := vc.NewVM(ctx, config)
@@ -160,24 +163,39 @@ func (t *template) createTemplateVM(ctx context.Context) error {
160163
return err
161164
}
162165

163-
if err = vm.Save(); err != nil {
166+
if err = vm.Save(t.statePath); err != nil {
164167
return err
165168
}
166169

170+
// The template source VM runs with shared memory so that clones can map
171+
// the same backing file, but the snapshot must record the memory as
172+
// private so that clones restored from it get Copy-On-Write memory. The
173+
// factory owns this policy decision (when to make a snapshot private),
174+
// while the CLH snapshot-format details live in
175+
// vc.PatchCLHSnapshotMemoryPrivate. Only Cloud Hypervisor records a
176+
// config.json that needs patching; QEMU's device-state file does not.
177+
if t.config.HypervisorType == vc.ClhHypervisor {
178+
if err = vc.PatchCLHSnapshotMemoryPrivate(t.statePath); err != nil {
179+
return err
180+
}
181+
}
182+
167183
return nil
168184
}
169185

170186
func (t *template) createFromTemplateVM(ctx context.Context, c vc.VMConfig) (*vc.VM, error) {
171187
config := t.config
172-
config.HypervisorConfig.BootToBeTemplate = false
173-
config.HypervisorConfig.BootFromTemplate = true
174-
config.HypervisorConfig.MemoryPath = t.statePath + "/memory"
175-
config.HypervisorConfig.DevicesStatePath = t.deviceStatePath()
188+
// Clones restored from the template use private Copy-On-Write memory
189+
// backed by the template's shared memory file.
190+
config.HypervisorConfig.FileBackedMemory = &vc.FileBackedMemoryConfig{
191+
Path: t.statePath + "/memory",
192+
Shared: false,
193+
}
176194
config.HypervisorConfig.SharedPath = c.HypervisorConfig.SharedPath
177195
config.HypervisorConfig.VMStorePath = c.HypervisorConfig.VMStorePath
178196
config.HypervisorConfig.RunStorePath = c.HypervisorConfig.RunStorePath
179197

180-
return vc.NewVM(ctx, config)
198+
return vc.NewVMFromSnapshot(ctx, config, t.statePath)
181199
}
182200

183201
func (t *template) checkTemplateVM() error {

src/runtime/virtcontainers/fc.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,13 @@ func (fc *firecracker) PauseVM(ctx context.Context) error {
947947
return nil
948948
}
949949

950-
func (fc *firecracker) SaveVM() error {
950+
func (fc *firecracker) SaveVM(snapshotDir string) error {
951+
// Firecracker does not support snapshot/restore in this implementation.
952+
return nil
953+
}
954+
955+
func (fc *firecracker) RestoreVM(ctx context.Context, snapshotDir string) error {
956+
// Firecracker does not support snapshot/restore in this implementation.
951957
return nil
952958
}
953959

src/runtime/virtcontainers/fc_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func TestFCSaveVM(t *testing.T) {
124124
assert := assert.New(t)
125125

126126
fc := firecracker{}
127-
err := fc.SaveVM()
127+
err := fc.SaveVM(t.TempDir())
128128
assert.NoError(err)
129129
}
130130

src/runtime/virtcontainers/hypervisor.go

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,22 @@ type Param struct {
463463
Value string
464464
}
465465

466+
// FileBackedMemoryConfig describes guest memory that is backed by a host file.
467+
// It is a generic primitive used by features such as VM templating, live
468+
// migration and checkpoint/restore. The hypervisor layer only needs to know
469+
// where the backing file lives and whether it should be mapped shared or
470+
// private; it does not need to know which higher-level feature requested it.
471+
type FileBackedMemoryConfig struct {
472+
// Path is the host path of the memory backing file.
473+
Path string
474+
475+
// Shared selects the mapping mode for the backing file: MAP_SHARED when
476+
// true (e.g. a template source whose memory is shared with clones) and
477+
// MAP_PRIVATE/Copy-On-Write when false (e.g. a clone restored from a
478+
// template or a migration target that needs its own private memory).
479+
Shared bool
480+
}
481+
466482
// HypervisorConfig is the hypervisor configuration.
467483
// nolint: govet
468484
type HypervisorConfig struct {
@@ -514,13 +530,12 @@ type HypervisorConfig struct {
514530
// emulated.
515531
HypervisorMachineType string
516532

517-
// MemoryPath is the memory file path of VM memory. Used when either BootToBeTemplate or
518-
// BootFromTemplate is true.
519-
MemoryPath string
520-
521-
// DevicesStatePath is the VM device state file path. Used when either BootToBeTemplate or
522-
// BootFromTemplate is true.
523-
DevicesStatePath string
533+
// FileBackedMemory describes file-backed guest memory. When non-nil the
534+
// guest memory is backed by the file at Path and mapped either MAP_SHARED
535+
// (Shared=true, e.g. a template source) or MAP_PRIVATE/Copy-On-Write
536+
// (Shared=false, e.g. a clone restored from a template or a migration
537+
// target). When nil the guest uses standard anonymous memory.
538+
FileBackedMemory *FileBackedMemoryConfig
524539

525540
// EntropySource is the path to a host source of
526541
// entropy (/dev/random, /dev/urandom or real hardware RNG device)
@@ -561,10 +576,20 @@ type HypervisorConfig struct {
561576
// VMid is "" if the hypervisor is not created by the factory.
562577
VMid string
563578

564-
// VMStorePath is the location on disk where VM information will persist
579+
// VMStorePath is the root directory (typically /run/vc/vm, supplied by the
580+
// persist driver's RunVMStoragePath()) under which each VM's VMM runtime
581+
// artifacts live at <VMStorePath>/<id>: the hypervisor's control sockets
582+
// (e.g. Cloud Hypervisor's API and hybrid-vsock sockets, QEMU's QMP and
583+
// console sockets), QEMU's pid file, and the config.json/state.json files
584+
// copied in when restoring a VM from a snapshot.
565585
VMStorePath string
566586

567-
// VMStorePath is the location on disk where runtime information will persist
587+
// RunStorePath is the root directory (typically /run/vc/sbs, supplied by
588+
// the persist driver's RunStoragePath()) under which each sandbox's
589+
// persisted state lives at <RunStorePath>/<id>: the sandbox and container
590+
// state written by the persist driver. (A guest memory dump reads this
591+
// state and copies it into the dump, but the dump itself — vmcore and
592+
// hypervisor metadata — is written under GuestMemoryDumpPath, not here.)
568593
RunStorePath string
569594

570595
// SELinux label for the VM
@@ -831,12 +856,6 @@ type HypervisorConfig struct {
831856
// Enable SEV-SNP guests on AMD machines capable of both
832857
SevSnpGuest bool
833858

834-
// BootToBeTemplate used to indicate if the VM is created to be a template VM
835-
BootToBeTemplate bool
836-
837-
// BootFromTemplate used to indicate if the VM should be created from a template VM
838-
BootFromTemplate bool
839-
840859
// DisableVhostNet is used to indicate if host supports vhost_net
841860
DisableVhostNet bool
842861

@@ -894,24 +913,6 @@ type VcpuThreadIDs struct {
894913
vcpus map[int]int
895914
}
896915

897-
func (conf *HypervisorConfig) CheckTemplateConfig() error {
898-
if conf.BootToBeTemplate && conf.BootFromTemplate {
899-
return fmt.Errorf("Cannot set both 'to be' and 'from' vm tempate")
900-
}
901-
902-
if conf.BootToBeTemplate || conf.BootFromTemplate {
903-
if conf.MemoryPath == "" {
904-
return fmt.Errorf("Missing MemoryPath for vm template")
905-
}
906-
907-
if conf.BootFromTemplate && conf.DevicesStatePath == "" {
908-
return fmt.Errorf("Missing DevicesStatePath to Load from vm template")
909-
}
910-
}
911-
912-
return nil
913-
}
914-
915916
// AddKernelParam allows the addition of new kernel parameters to an existing
916917
// hypervisor configuration.
917918
func (conf *HypervisorConfig) AddKernelParam(p Param) error {
@@ -1311,7 +1312,16 @@ type Hypervisor interface {
13111312
// just perform cleanup.
13121313
StopVM(ctx context.Context, waitOnly bool) error
13131314
PauseVM(ctx context.Context) error
1314-
SaveVM() error
1315+
// SaveVM snapshots the running VM into snapshotDir. It is a pure
1316+
// hypervisor operation: the caller chooses the destination and is
1317+
// responsible for any feature-specific post-processing (e.g. template
1318+
// memory-sharing adjustments).
1319+
SaveVM(snapshotDir string) error
1320+
// RestoreVM brings up a VM by restoring it from a snapshot previously
1321+
// written to snapshotDir. The restored VM is left in a paused state; the
1322+
// caller decides when to ResumeVM and what post-restore housekeeping to
1323+
// perform (e.g. reseeding the RNG, syncing the guest clock).
1324+
RestoreVM(ctx context.Context, snapshotDir string) error
13151325
ResumeVM(ctx context.Context) error
13161326
AddDevice(ctx context.Context, devInfo interface{}, devType DeviceType) error
13171327
HotplugAddDevice(ctx context.Context, devInfo interface{}, devType DeviceType) (interface{}, error)

src/runtime/virtcontainers/hypervisor_config_linux.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ func validateHypervisorConfig(conf *HypervisorConfig) error {
3232
return fmt.Errorf("Image and initrd path cannot be both set")
3333
}
3434

35-
if err := conf.CheckTemplateConfig(); err != nil {
36-
return err
37-
}
38-
3935
if conf.NumVCPUsF == 0 {
4036
conf.NumVCPUsF = defaultVCPUs
4137
}

src/runtime/virtcontainers/hypervisor_config_linux_test.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,30 +65,6 @@ func TestHypervisorConfigSecureExecution(t *testing.T) {
6565
testHypervisorConfigValid(t, hypervisorConfig, false)
6666
}
6767

68-
func TestHypervisorConfigValidTemplateConfig(t *testing.T) {
69-
hypervisorConfig := &HypervisorConfig{
70-
KernelPath: fmt.Sprintf("%s/%s", testDir, testKernel),
71-
ImagePath: fmt.Sprintf("%s/%s", testDir, testImage),
72-
HypervisorPath: fmt.Sprintf("%s/%s", testDir, testHypervisor),
73-
BootToBeTemplate: true,
74-
BootFromTemplate: true,
75-
}
76-
testHypervisorConfigValid(t, hypervisorConfig, false)
77-
78-
hypervisorConfig.BootToBeTemplate = false
79-
testHypervisorConfigValid(t, hypervisorConfig, false)
80-
hypervisorConfig.MemoryPath = "foobar"
81-
testHypervisorConfigValid(t, hypervisorConfig, false)
82-
hypervisorConfig.DevicesStatePath = "foobar"
83-
testHypervisorConfigValid(t, hypervisorConfig, true)
84-
85-
hypervisorConfig.BootFromTemplate = false
86-
hypervisorConfig.BootToBeTemplate = true
87-
testHypervisorConfigValid(t, hypervisorConfig, true)
88-
hypervisorConfig.MemoryPath = ""
89-
testHypervisorConfigValid(t, hypervisorConfig, false)
90-
}
91-
9268
func TestHypervisorConfigDefaults(t *testing.T) {
9369
assert := assert.New(t)
9470
hypervisorConfig := &HypervisorConfig{

src/runtime/virtcontainers/mock_hypervisor.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ func (m *mockHypervisor) ResumeVM(ctx context.Context) error {
6161
return nil
6262
}
6363

64-
func (m *mockHypervisor) SaveVM() error {
64+
func (m *mockHypervisor) SaveVM(snapshotDir string) error {
65+
return nil
66+
}
67+
68+
func (m *mockHypervisor) RestoreVM(ctx context.Context, snapshotDir string) error {
6569
return nil
6670
}
6771

0 commit comments

Comments
 (0)