Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion hypervisor/cloudhypervisor/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,18 @@ func serialConsoleFor(directBoot bool, consoleSock string) (serial, console *chR
return &chRuntimeFile{Mode: "Socket", Socket: consoleSock}, &chRuntimeFile{Mode: "Off"}
}

func qcow2Overlay(sc *types.StorageConfig) bool {
return !sc.RO && filepath.Ext(sc.Path) == ".qcow2"
}

func effectiveDirectIO(sc *types.StorageConfig, noDirectIO bool) bool {
if sc.DirectIO != nil {
return *sc.DirectIO
}
// CH applies this flag to the backing file too, so O_DIRECT would stop one page-cache copy of the shared base serving every VM.
if qcow2Overlay(sc) {
return false
}
return !sc.RO && !noDirectIO
}

Expand All @@ -201,7 +209,7 @@ func storageConfigToDisk(storageConfig *types.StorageConfig, cpuCount, diskQueue
switch {
case filepath.Ext(storageConfig.Path) == ".qcow2":
d.ImageType = "Qcow2"
d.BackingFiles = !storageConfig.RO
d.BackingFiles = qcow2Overlay(storageConfig)
case storageConfig.RO:
d.ImageType = "Raw"
default:
Expand Down
36 changes: 36 additions & 0 deletions hypervisor/cloudhypervisor/args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,39 @@ func TestMemoryCLIArg(t *testing.T) {
})
}
}

func TestEffectiveDirectIO(t *testing.T) {
tests := []struct {
name string
sc types.StorageConfig
noDirectIO bool
want bool
}{
{name: "raw cow", sc: types.StorageConfig{Path: "/v/cow.raw", Role: types.StorageRoleCOW}, want: true},
{name: "raw cow with no-direct-io", sc: types.StorageConfig{Path: "/v/cow.raw", Role: types.StorageRoleCOW}, noDirectIO: true},
{name: "readonly layer", sc: types.StorageConfig{Path: "/v/base.raw", RO: true, Role: types.StorageRoleLayer}},
{name: "qcow2 overlay stays buffered", sc: types.StorageConfig{Path: "/v/overlay.qcow2", Role: types.StorageRoleCOW}},
{name: "readonly qcow2 has no backing chain", sc: types.StorageConfig{Path: "/v/base.qcow2", RO: true, Role: types.StorageRoleLayer}},
{name: "explicit override wins", sc: types.StorageConfig{Path: "/v/data.raw", Role: types.StorageRoleData, DirectIO: ptr(false)}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := effectiveDirectIO(&tt.sc, tt.noDirectIO); got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}

func TestQcow2OverlayDiskArgs(t *testing.T) {
sc := &types.StorageConfig{Path: "/v/overlay.qcow2", Role: types.StorageRoleCOW}
got := diskToCLIArg(storageConfigToDisk(sc, 1, 0, false, nil))
if strings.Contains(got, "direct=on") {
t.Errorf("qcow2 overlay must stay buffered so the shared base keeps one page-cache copy: %s", got)
}
if !strings.Contains(got, "backing_files=on") {
t.Errorf("qcow2 overlay must keep backing_files=on: %s", got)
}
}

func ptr[T any](v T) *T { return &v }
18 changes: 9 additions & 9 deletions meta/sqlite/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,6 @@ func OpenForRecovery(dbPath string, namespaces ...Namespace) (*Store, error) {
return openStore(dbPath, namespaces)
}

// RefuseManifest fails when a conversion manifest sits beside dbPath, meaning an offline conversion is unfinished.
func RefuseManifest(dbPath string) error {
manifest := filepath.Join(filepath.Dir(dbPath), ManifestName)
if utils.FileExists(manifest) {
return fmt.Errorf("%s exists: a conversion is in flight, run `cocoon meta convert` to finish it", manifest)
}
return nil
}

func openStore(dbPath string, namespaces []Namespace) (*Store, error) {
// The driver creates a file on first touch; Open never creates — that is Init's job (§6) — and §4 refuses network filesystems before WAL work.
if !utils.FileExists(dbPath) {
Expand Down Expand Up @@ -381,6 +372,15 @@ func (h *txHandle) checkRead(ns string) error {
return nil
}

// RefuseManifest fails when a conversion manifest sits beside dbPath, meaning an offline conversion is unfinished.
func RefuseManifest(dbPath string) error {
manifest := filepath.Join(filepath.Dir(dbPath), ManifestName)
if utils.FileExists(manifest) {
return fmt.Errorf("%s exists: a conversion is in flight, run `cocoon meta convert` to finish it", manifest)
}
return nil
}

func tableName(ns, table string) string {
return quoteIdent(ns + "__" + table)
}
Expand Down