Skip to content

Commit e449d53

Browse files
committed
refactor: extract shared hypervisor BaseConfig and CloneSetup
CH and FC config had 12 identical methods (dir layout, timeouts, EnsureDirs) differing only in the backend name string. Extract hypervisor.BaseConfig with a backendName field; both backends embed it and keep only their unique methods (BinaryName, PIDFileName, backend-specific paths). cloneSetup was also character-for-character identical in both backends (validate CPU, backfill image, reserve record, create dirs, build cleanup func). Promote to Backend.CloneSetup so both Clone and DirectClone in each backend call the shared implementation. Net: ~70 lines removed, zero behavior change.
1 parent 98c6139 commit e449d53

10 files changed

Lines changed: 127 additions & 180 deletions

File tree

hypervisor/backend.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,37 @@ func (b *Backend) ReserveVM(ctx context.Context, id string, vmCfg *types.VMConfi
194194
})
195195
}
196196

197+
// CloneSetup handles the shared pre-clone sequence used by both
198+
// backends' Clone and DirectClone entry points: validate CPU, backfill
199+
// image ref from snapshot, reserve a placeholder record, create dirs,
200+
// and return a cleanup function.
201+
func (b *Backend) CloneSetup(ctx context.Context, vmID string, vmCfg *types.VMConfig, snapshotConfig *types.SnapshotConfig) (runDir, logDir string, now time.Time, cleanup func(), err error) {
202+
if err = ValidateHostCPU(vmCfg.CPU); err != nil {
203+
return "", "", time.Time{}, nil, err
204+
}
205+
if vmCfg.Image == "" && snapshotConfig.Image != "" {
206+
vmCfg.Image = snapshotConfig.Image
207+
}
208+
209+
now = time.Now()
210+
runDir = b.Conf.VMRunDir(vmID)
211+
logDir = b.Conf.VMLogDir(vmID)
212+
213+
cleanup = func() {
214+
_ = RemoveVMDirs(runDir, logDir)
215+
b.RollbackCreate(ctx, vmID, vmCfg.Name)
216+
}
217+
218+
if err = b.ReserveVM(ctx, vmID, vmCfg, snapshotConfig.ImageBlobIDs, runDir, logDir); err != nil {
219+
return "", "", time.Time{}, nil, fmt.Errorf("reserve VM record: %w", err)
220+
}
221+
if err = utils.EnsureDirs(runDir, logDir); err != nil {
222+
cleanup()
223+
return "", "", time.Time{}, nil, fmt.Errorf("ensure dirs: %w", err)
224+
}
225+
return runDir, logDir, now, cleanup, nil
226+
}
227+
197228
// RollbackCreate removes a placeholder VM record from the DB.
198229
func (b *Backend) RollbackCreate(ctx context.Context, id, name string) {
199230
if err := b.DB.Update(ctx, func(idx *VMIndex) error {

hypervisor/baseconfig.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package hypervisor
2+
3+
import (
4+
"path/filepath"
5+
"time"
6+
7+
"github.com/cocoonstack/cocoon/config"
8+
"github.com/cocoonstack/cocoon/utils"
9+
)
10+
11+
const (
12+
defaultSocketWaitTimeout = 5 * time.Second
13+
defaultTerminateGracePeriod = 5 * time.Second
14+
)
15+
16+
// BaseConfig holds the directory layout and timeout defaults shared by
17+
// all hypervisor backends. Each backend embeds BaseConfig and adds
18+
// backend-specific methods (BinaryName, PIDFileName, etc.).
19+
type BaseConfig struct {
20+
*config.Config
21+
backendName string
22+
}
23+
24+
// NewBaseConfig creates a BaseConfig for the named backend.
25+
func NewBaseConfig(conf *config.Config, name string) BaseConfig {
26+
return BaseConfig{Config: conf, backendName: name}
27+
}
28+
29+
func (c *BaseConfig) dir() string { return filepath.Join(c.RootDir, c.backendName) }
30+
func (c *BaseConfig) dbDir() string { return filepath.Join(c.dir(), "db") }
31+
32+
// RunDir returns the top-level runtime directory for this backend.
33+
func (c *BaseConfig) RunDir() string { return filepath.Join(c.Config.RunDir, c.backendName) }
34+
35+
// LogDir returns the top-level log directory for this backend.
36+
func (c *BaseConfig) LogDir() string { return filepath.Join(c.Config.LogDir, c.backendName) }
37+
38+
// IndexFile returns the VM index store path.
39+
func (c *BaseConfig) IndexFile() string { return filepath.Join(c.dbDir(), "vms.json") }
40+
41+
// IndexLock returns the VM index lock path.
42+
func (c *BaseConfig) IndexLock() string { return filepath.Join(c.dbDir(), "vms.lock") }
43+
44+
// VMRunDir returns the per-VM runtime directory.
45+
func (c *BaseConfig) VMRunDir(vmID string) string { return filepath.Join(c.RunDir(), vmID) }
46+
47+
// VMLogDir returns the per-VM log directory.
48+
func (c *BaseConfig) VMLogDir(vmID string) string { return filepath.Join(c.LogDir(), vmID) }
49+
50+
// EnsureDirs creates all static directories required by the backend.
51+
func (c *BaseConfig) EnsureDirs() error {
52+
return utils.EnsureDirs(c.dbDir(), c.RunDir(), c.LogDir())
53+
}
54+
55+
// SocketWaitTimeout returns the configured socket wait timeout or the default (5s).
56+
func (c *BaseConfig) SocketWaitTimeout() time.Duration {
57+
if c.SocketWaitTimeoutSeconds > 0 {
58+
return time.Duration(c.SocketWaitTimeoutSeconds) * time.Second
59+
}
60+
return defaultSocketWaitTimeout
61+
}
62+
63+
// TerminateGracePeriod returns the configured SIGTERM→SIGKILL grace period or the default (5s).
64+
func (c *BaseConfig) TerminateGracePeriod() time.Duration {
65+
if c.TerminateGracePeriodSeconds > 0 {
66+
return time.Duration(c.TerminateGracePeriodSeconds) * time.Second
67+
}
68+
return defaultTerminateGracePeriod
69+
}

hypervisor/cloudhypervisor/clone.go

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121

2222
// Clone creates a new VM from a snapshot tar stream.
2323
func (ch *CloudHypervisor) Clone(ctx context.Context, vmID string, vmCfg *types.VMConfig, networkConfigs []*types.NetworkConfig, snapshotConfig *types.SnapshotConfig, snapshot io.Reader) (_ *types.VM, err error) {
24-
runDir, logDir, now, cleanup, err := ch.cloneSetup(ctx, vmID, vmCfg, snapshotConfig)
24+
runDir, logDir, now, cleanup, err := ch.CloneSetup(ctx, vmID, vmCfg, snapshotConfig)
2525
if err != nil {
2626
return nil, err
2727
}
@@ -38,35 +38,6 @@ func (ch *CloudHypervisor) Clone(ctx context.Context, vmID string, vmCfg *types.
3838
return ch.cloneAfterExtract(ctx, vmID, vmCfg, networkConfigs, runDir, logDir, now)
3939
}
4040

41-
func (ch *CloudHypervisor) cloneSetup(ctx context.Context, vmID string, vmCfg *types.VMConfig, snapshotConfig *types.SnapshotConfig) (runDir, logDir string, now time.Time, cleanup func(), err error) {
42-
// Shared validation for stream and direct clone.
43-
if err = hypervisor.ValidateHostCPU(vmCfg.CPU); err != nil {
44-
return "", "", time.Time{}, nil, err
45-
}
46-
47-
if vmCfg.Image == "" && snapshotConfig.Image != "" {
48-
vmCfg.Image = snapshotConfig.Image
49-
}
50-
51-
now = time.Now()
52-
runDir = ch.conf.VMRunDir(vmID)
53-
logDir = ch.conf.VMLogDir(vmID)
54-
55-
cleanup = func() {
56-
_ = hypervisor.RemoveVMDirs(runDir, logDir)
57-
ch.RollbackCreate(ctx, vmID, vmCfg.Name)
58-
}
59-
60-
if err = ch.ReserveVM(ctx, vmID, vmCfg, snapshotConfig.ImageBlobIDs, runDir, logDir); err != nil {
61-
return "", "", time.Time{}, nil, fmt.Errorf("reserve VM record: %w", err)
62-
}
63-
if err = utils.EnsureDirs(runDir, logDir); err != nil {
64-
cleanup()
65-
return "", "", time.Time{}, nil, fmt.Errorf("ensure dirs: %w", err)
66-
}
67-
return runDir, logDir, now, cleanup, nil
68-
}
69-
7041
// cloneAfterExtract resumes from snapshot data already placed in runDir.
7142
func (ch *CloudHypervisor) cloneAfterExtract(ctx context.Context, vmID string, vmCfg *types.VMConfig, networkConfigs []*types.NetworkConfig, runDir, logDir string, now time.Time) (*types.VM, error) {
7243
logger := log.WithFunc("cloudhypervisor.Clone")

hypervisor/cloudhypervisor/cloudhypervisor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func New(conf *config.Config) (*CloudHypervisor, error) {
3131
if conf == nil {
3232
return nil, fmt.Errorf("config is nil")
3333
}
34-
cfg := &Config{Config: conf}
34+
cfg := NewConfig(conf)
3535
if err := cfg.EnsureDirs(); err != nil {
3636
return nil, fmt.Errorf("ensure dirs: %w", err)
3737
}

hypervisor/cloudhypervisor/config.go

Lines changed: 11 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,26 @@ package cloudhypervisor
22

33
import (
44
"path/filepath"
5-
"time"
65

76
"github.com/cocoonstack/cocoon/config"
8-
"github.com/cocoonstack/cocoon/utils"
7+
"github.com/cocoonstack/cocoon/hypervisor"
98
)
109

11-
// BinaryName returns the base name of the Cloud Hypervisor binary.
12-
func (c *Config) BinaryName() string { return filepath.Base(c.CHBinary) }
13-
14-
// PIDFileName returns the PID file name for the Cloud Hypervisor backend.
15-
func (c *Config) PIDFileName() string { return "ch.pid" }
16-
17-
const (
18-
defaultSocketWaitTimeout = 5 * time.Second
19-
defaultTerminateGracePeriod = 5 * time.Second
20-
)
21-
22-
// Config holds Cloud Hypervisor specific configuration, embedding the global config.
10+
// Config holds Cloud Hypervisor specific configuration.
2311
type Config struct {
24-
*config.Config
12+
hypervisor.BaseConfig
2513
}
2614

27-
// EnsureDirs creates all static directories required by the Cloud Hypervisor backend.
28-
func (c *Config) EnsureDirs() error {
29-
return utils.EnsureDirs(
30-
c.dbDir(),
31-
c.RunDir(),
32-
c.LogDir(),
33-
)
15+
// NewConfig creates a Config from a global config.
16+
func NewConfig(conf *config.Config) *Config {
17+
return &Config{BaseConfig: hypervisor.NewBaseConfig(conf, "cloudhypervisor")}
3418
}
3519

36-
// RunDir returns the top-level CH runtime directory.
37-
func (c *Config) RunDir() string { return filepath.Join(c.Config.RunDir, "cloudhypervisor") }
38-
39-
// LogDir returns the top-level CH log directory.
40-
func (c *Config) LogDir() string { return filepath.Join(c.Config.LogDir, "cloudhypervisor") }
41-
42-
// IndexFile returns the VM index store path.
43-
func (c *Config) IndexFile() string { return filepath.Join(c.dbDir(), "vms.json") }
44-
45-
// IndexLock returns the VM index lock path.
46-
func (c *Config) IndexLock() string { return filepath.Join(c.dbDir(), "vms.lock") }
47-
48-
// VMRunDir returns the per-VM runtime directory.
49-
func (c *Config) VMRunDir(vmID string) string { return filepath.Join(c.RunDir(), vmID) }
20+
// BinaryName returns the base name of the Cloud Hypervisor binary.
21+
func (c *Config) BinaryName() string { return filepath.Base(c.CHBinary) }
5022

51-
// VMLogDir returns the per-VM log directory.
52-
func (c *Config) VMLogDir(vmID string) string { return filepath.Join(c.LogDir(), vmID) }
23+
// PIDFileName returns the PID file name for the Cloud Hypervisor backend.
24+
func (c *Config) PIDFileName() string { return "ch.pid" }
5325

5426
// COWRawPath returns the path for the OCI COW raw disk.
5527
func (c *Config) COWRawPath(vmID string) string {
@@ -63,24 +35,5 @@ func (c *Config) OverlayPath(vmID string) string {
6335

6436
// CidataPath returns the path for the cloud-init NoCloud cidata disk.
6537
func (c *Config) CidataPath(vmID string) string {
66-
return filepath.Join(c.VMRunDir(vmID), "cidata.img")
38+
return filepath.Join(c.VMRunDir(vmID), cidataFile)
6739
}
68-
69-
// SocketWaitTimeout returns the configured socket wait timeout or the default.
70-
func (c *Config) SocketWaitTimeout() time.Duration {
71-
if c.SocketWaitTimeoutSeconds > 0 {
72-
return time.Duration(c.SocketWaitTimeoutSeconds) * time.Second
73-
}
74-
return defaultSocketWaitTimeout
75-
}
76-
77-
// TerminateGracePeriod returns the configured SIGTERM→SIGKILL grace period or the default.
78-
func (c *Config) TerminateGracePeriod() time.Duration {
79-
if c.TerminateGracePeriodSeconds > 0 {
80-
return time.Duration(c.TerminateGracePeriodSeconds) * time.Second
81-
}
82-
return defaultTerminateGracePeriod
83-
}
84-
85-
func (c *Config) dir() string { return filepath.Join(c.RootDir, "cloudhypervisor") }
86-
func (c *Config) dbDir() string { return filepath.Join(c.dir(), "db") }

hypervisor/cloudhypervisor/direct.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
// Files are handled per-type: hardlink for memory-range-*, reflink/copy for
1717
// the COW disk, plain copy for small metadata, and cidata is regenerated.
1818
func (ch *CloudHypervisor) DirectClone(ctx context.Context, vmID string, vmCfg *types.VMConfig, networkConfigs []*types.NetworkConfig, snapshotConfig *types.SnapshotConfig, srcDir string) (_ *types.VM, err error) {
19-
runDir, logDir, now, cleanup, err := ch.cloneSetup(ctx, vmID, vmCfg, snapshotConfig)
19+
runDir, logDir, now, cleanup, err := ch.CloneSetup(ctx, vmID, vmCfg, snapshotConfig)
2020
if err != nil {
2121
return nil, err
2222
}

hypervisor/firecracker/clone.go

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717

1818
// Clone creates a new VM from a snapshot tar stream via FC snapshot/load.
1919
func (fc *Firecracker) Clone(ctx context.Context, vmID string, vmCfg *types.VMConfig, networkConfigs []*types.NetworkConfig, snapshotConfig *types.SnapshotConfig, snapshot io.Reader) (_ *types.VM, err error) {
20-
runDir, logDir, now, cleanup, err := fc.cloneSetup(ctx, vmID, vmCfg, snapshotConfig)
20+
runDir, logDir, now, cleanup, err := fc.CloneSetup(ctx, vmID, vmCfg, snapshotConfig)
2121
if err != nil {
2222
return nil, err
2323
}
@@ -34,36 +34,6 @@ func (fc *Firecracker) Clone(ctx context.Context, vmID string, vmCfg *types.VMCo
3434
return fc.cloneAfterExtract(ctx, vmID, vmCfg, networkConfigs, runDir, logDir, now)
3535
}
3636

37-
func (fc *Firecracker) cloneSetup(ctx context.Context, vmID string, vmCfg *types.VMConfig, snapshotConfig *types.SnapshotConfig) (runDir, logDir string, now time.Time, cleanup func(), err error) {
38-
// Shared validation point for Clone (stream) and DirectClone
39-
// (local dir) — both enter here via cloneSetup.
40-
if err = hypervisor.ValidateHostCPU(vmCfg.CPU); err != nil {
41-
return "", "", time.Time{}, nil, err
42-
}
43-
44-
if vmCfg.Image == "" && snapshotConfig.Image != "" {
45-
vmCfg.Image = snapshotConfig.Image
46-
}
47-
48-
now = time.Now()
49-
runDir = fc.conf.VMRunDir(vmID)
50-
logDir = fc.conf.VMLogDir(vmID)
51-
52-
cleanup = func() {
53-
_ = hypervisor.RemoveVMDirs(runDir, logDir)
54-
fc.RollbackCreate(ctx, vmID, vmCfg.Name)
55-
}
56-
57-
if err = fc.ReserveVM(ctx, vmID, vmCfg, snapshotConfig.ImageBlobIDs, runDir, logDir); err != nil {
58-
return "", "", time.Time{}, nil, fmt.Errorf("reserve VM record: %w", err)
59-
}
60-
if err = utils.EnsureDirs(runDir, logDir); err != nil {
61-
cleanup()
62-
return "", "", time.Time{}, nil, fmt.Errorf("ensure dirs: %w", err)
63-
}
64-
return runDir, logDir, now, cleanup, nil
65-
}
66-
6737
// cloneAfterExtract contains all clone logic after snapshot data is in runDir.
6838
// FC snapshots require the same directory layout — paths are absolute.
6939
func (fc *Firecracker) cloneAfterExtract(ctx context.Context, vmID string, vmCfg *types.VMConfig, networkConfigs []*types.NetworkConfig, runDir, logDir string, now time.Time) (*types.VM, error) {

hypervisor/firecracker/config.go

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,68 +2,19 @@ package firecracker
22

33
import (
44
"path/filepath"
5-
"time"
65

76
"github.com/cocoonstack/cocoon/config"
8-
"github.com/cocoonstack/cocoon/utils"
7+
"github.com/cocoonstack/cocoon/hypervisor"
98
)
109

11-
const (
12-
defaultSocketWaitTimeout = 5 * time.Second
13-
defaultTerminateGracePeriod = 5 * time.Second
14-
)
15-
16-
// Config holds Firecracker specific configuration, embedding the global config.
10+
// Config holds Firecracker specific configuration.
1711
type Config struct {
18-
*config.Config
12+
hypervisor.BaseConfig
1913
}
2014

21-
// EnsureDirs creates all static directories required by the Firecracker backend.
22-
func (c *Config) EnsureDirs() error {
23-
return utils.EnsureDirs(
24-
c.dbDir(),
25-
c.RunDir(),
26-
c.LogDir(),
27-
)
28-
}
29-
30-
// RunDir returns the top-level FC runtime directory.
31-
func (c *Config) RunDir() string { return filepath.Join(c.Config.RunDir, "firecracker") }
32-
33-
// LogDir returns the top-level FC log directory.
34-
func (c *Config) LogDir() string { return filepath.Join(c.Config.LogDir, "firecracker") }
35-
36-
// IndexFile returns the VM index store path.
37-
func (c *Config) IndexFile() string { return filepath.Join(c.dbDir(), "vms.json") }
38-
39-
// IndexLock returns the VM index lock path.
40-
func (c *Config) IndexLock() string { return filepath.Join(c.dbDir(), "vms.lock") }
41-
42-
// VMRunDir returns the per-VM runtime directory.
43-
func (c *Config) VMRunDir(vmID string) string { return filepath.Join(c.RunDir(), vmID) }
44-
45-
// VMLogDir returns the per-VM log directory.
46-
func (c *Config) VMLogDir(vmID string) string { return filepath.Join(c.LogDir(), vmID) }
47-
48-
// COWRawPath returns the path for the OCI COW raw disk.
49-
func (c *Config) COWRawPath(vmID string) string {
50-
return filepath.Join(c.VMRunDir(vmID), cowFileName)
51-
}
52-
53-
// SocketWaitTimeout returns the configured socket wait timeout or the default.
54-
func (c *Config) SocketWaitTimeout() time.Duration {
55-
if c.SocketWaitTimeoutSeconds > 0 {
56-
return time.Duration(c.SocketWaitTimeoutSeconds) * time.Second
57-
}
58-
return defaultSocketWaitTimeout
59-
}
60-
61-
// TerminateGracePeriod returns the configured SIGTERM→SIGKILL grace period or the default.
62-
func (c *Config) TerminateGracePeriod() time.Duration {
63-
if c.TerminateGracePeriodSeconds > 0 {
64-
return time.Duration(c.TerminateGracePeriodSeconds) * time.Second
65-
}
66-
return defaultTerminateGracePeriod
15+
// NewConfig creates a Config from a global config.
16+
func NewConfig(conf *config.Config) *Config {
17+
return &Config{BaseConfig: hypervisor.NewBaseConfig(conf, "firecracker")}
6718
}
6819

6920
// BinaryName returns the base name of the Firecracker binary.
@@ -72,5 +23,7 @@ func (c *Config) BinaryName() string { return filepath.Base(c.FCBinary) }
7223
// PIDFileName returns the PID file name for the Firecracker backend.
7324
func (c *Config) PIDFileName() string { return pidFileName }
7425

75-
func (c *Config) dir() string { return filepath.Join(c.RootDir, "firecracker") }
76-
func (c *Config) dbDir() string { return filepath.Join(c.dir(), "db") }
26+
// COWRawPath returns the path for the OCI COW raw disk.
27+
func (c *Config) COWRawPath(vmID string) string {
28+
return filepath.Join(c.VMRunDir(vmID), cowFileName)
29+
}

0 commit comments

Comments
 (0)