Skip to content
Merged
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
11 changes: 6 additions & 5 deletions docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ the cocoon CLI and needs a template image with silkd baked in.
## Prerequisites

- Linux with KVM (`/dev/kvm`)
- [cocoon](https://github.com/cocoonstack/cocoon) **v0.5.2 or newer** installed
and working (`cocoon vm run` boots a Cloud Hypervisor VM). v0.5.2 includes
- [cocoon](https://github.com/cocoonstack/cocoon) **v0.6.9 or newer** installed
and working (`cocoon vm run` boots a Cloud Hypervisor VM). v0.5.2 brought
the disk hot-attach used by read-only volumes and the parallel-clone and
snapshot/store performance work the
[performance](performance.md) numbers assume. sandboxd logs a warning at
startup when the detected cocoon is below v0.5.2 (a dev/`master-<sha>` build
is assumed current)
[performance](performance.md) numbers assume; v0.6.9 prints `[]` for an
empty `snapshot list --format json`, which sandboxd's snapshot reconcile
reads. sandboxd logs a warning at startup when the detected cocoon is below
v0.6.9 (a dev/`master-<sha>` build is assumed current)
- The sandbox boot artifact installed where cocoon finds it
(`/boot/vmlinuz-sandbox`, `/boot/initrd.img-sandbox` — from
`ghcr.io/cocoonstack/sandbox/boot:<kernel-ver>`)
Expand Down
5 changes: 1 addition & 4 deletions e2e/fakeengine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,11 @@ func (f *fakeEngine) Restore(_ context.Context, name, _ string) (string, error)
return f.create(name)
}

func (f *fakeEngine) List(_ context.Context, filters ...string) ([]types.VMRecord, error) {
func (f *fakeEngine) List(_ context.Context) ([]types.VMRecord, error) {
f.mu.Lock()
defer f.mu.Unlock()
var vms []types.VMRecord
for name, sock := range f.socks {
if len(filters) > 0 && !slices.Contains(filters, name) {
continue
}
vms = append(vms, types.VMRecord{State: "running", VsockSocket: sock, Config: types.VMConfig{Name: name}})
}
return vms, nil
Expand Down
16 changes: 5 additions & 11 deletions sandboxd/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import (
)

const (
// RequiredCocoon carries the snapshot/store performance baseline.
RequiredCocoon = "v0.5.2"
// RequiredCocoon carries the snapshot/store baseline and the JSON form of an empty snapshot list.
RequiredCocoon = "v0.6.9"

StaleCreateCollected StaleCreateOutcome = "collected"
StaleCreateBusy StaleCreateOutcome = "busy"
Expand Down Expand Up @@ -178,11 +178,6 @@ func (e *Engine) SnapshotList(ctx context.Context) ([]string, error) {
if err != nil {
return nil, err
}
// an empty store prints a human line ("No snapshots found."), not JSON.
out = bytes.TrimSpace(out)
if len(out) == 0 || out[0] != '[' {
return nil, nil
}
var snaps []struct {
Name string `json:"name"`
}
Expand All @@ -198,10 +193,9 @@ func (e *Engine) SnapshotList(ctx context.Context) ([]string, error) {
return names, nil
}

// List returns cocoon's view of local VMs, optionally filtered by name.
func (e *Engine) List(ctx context.Context, filters ...string) ([]types.VMRecord, error) {
args := append([]string{"vm", "list", "--format", formatJSON}, filters...)
out, err := e.run(ctx, args...)
// List returns cocoon's view of local VMs.
func (e *Engine) List(ctx context.Context) ([]types.VMRecord, error) {
out, err := e.run(ctx, "vm", "list", "--format", formatJSON)
if err != nil {
return nil, err
}
Expand Down
32 changes: 32 additions & 0 deletions sandboxd/engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,38 @@ func TestDialGuestPortCtxCancel(t *testing.T) {
}
}

func TestSnapshotListReadsAnEmptyStoreAndRejectsProse(t *testing.T) {
dir := t.TempDir()
script := "#!/bin/sh\nif [ \"$COCOON_TEST_PROSE\" = 1 ]; then echo 'No snapshots found.'; else echo '[]'; fi\n"
if err := os.WriteFile(filepath.Join(dir, "cocoon"), []byte(script), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
e := New("cocoon", nil, nil, false, false, "")

t.Setenv("COCOON_TEST_PROSE", "0")
if snaps, err := e.SnapshotList(t.Context()); err != nil || len(snaps) != 0 {
t.Fatalf("empty store: snaps=%v err=%v, want none and nil", snaps, err)
}
t.Setenv("COCOON_TEST_PROSE", "1")
if _, err := e.SnapshotList(t.Context()); err == nil || !strings.Contains(err.Error(), "parse snapshot list") {
t.Fatalf("prose banner: err=%v, want a parse error", err)
}
}

func TestListPassesNoPositionalArgsToCocoon(t *testing.T) {
dir := t.TempDir()
script := "#!/bin/sh\n[ \"$#\" = 4 ] || { echo \"unknown command $4 for cocoon vm list\" >&2; exit 1; }\necho '[{\"config\":{\"name\":\"sbx-1\"},\"state\":\"running\"}]'\n"
if err := os.WriteFile(filepath.Join(dir, "cocoon"), []byte(script), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
vms, err := New("cocoon", nil, nil, false, false, "").List(t.Context())
if err != nil || len(vms) != 1 || vms[0].Config.Name != "sbx-1" {
t.Fatalf("List = %+v, %v; want the one VM cocoon printed", vms, err)
}
}

func TestProbeTimeout(t *testing.T) {
path := sockPath(t)

Expand Down
10 changes: 5 additions & 5 deletions sandboxd/engine/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ func TestBelowFloor(t *testing.T) {
below bool
comparable bool
}{
{"v0.5.2", false, true},
{"v0.5.3", false, true},
{"v0.6.0", false, true},
{"v0.5.1", true, true},
{"v0.5.0", true, true},
{"v0.6.9", false, true},
{"v0.6.10", false, true},
{"v0.7.0", false, true},
{"v0.6.8", true, true},
{"v0.5.2", true, true},
{"v0.4.9", true, true},
{"v1.0.0", false, true},
{"v0.10.0", false, true},
Expand Down
2 changes: 1 addition & 1 deletion sandboxd/pool/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ type Engine interface {
SnapshotList(ctx context.Context) ([]string, error)
Hibernate(ctx context.Context, vmName, snapName string) error
Restore(ctx context.Context, vmName, snapRef string) (string, error)
List(ctx context.Context, filters ...string) ([]types.VMRecord, error)
List(ctx context.Context) ([]types.VMRecord, error)
Probe(ctx context.Context, vsockSocket string, timeout time.Duration) error
DialGuestPort(ctx context.Context, vsockSocket string, port uint16) (net.Conn, error)
InstallCACert(ctx context.Context, vsockSocket string, certPEM []byte) error
Expand Down
5 changes: 1 addition & 4 deletions sandboxd/pool/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1230,15 +1230,12 @@ func (f *fakeEngine) Restore(_ context.Context, name, _ string) (string, error)
return f.lateVsock(f.vms[name]), nil
}

func (f *fakeEngine) List(_ context.Context, filters ...string) ([]types.VMRecord, error) {
func (f *fakeEngine) List(_ context.Context) ([]types.VMRecord, error) {
f.mu.Lock()
defer f.mu.Unlock()
f.listCount++
var vms []types.VMRecord
for name, sock := range f.vms {
if len(filters) > 0 && !slices.Contains(filters, name) {
continue
}
sock = f.lateVsock(sock)
state := vmStateRunning
switch {
Expand Down
2 changes: 1 addition & 1 deletion sandboxd/pool/refill.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ func (m *Manager) vsockOf(ctx context.Context, name string) (string, error) {
}

func (m *Manager) findVM(ctx context.Context, name string) (types.VMRecord, bool, error) {
vms, err := m.eng.List(ctx, name)
vms, err := m.eng.List(ctx)
if err != nil {
return types.VMRecord{}, false, err
}
Expand Down
Loading