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
18 changes: 9 additions & 9 deletions hypervisor/firecracker/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ func (fc *Firecracker) prepareOCI(ctx context.Context, vmID string, vmCfg *types
return storageConfigs, nil
}

// DevPath maps idx to vda..vdz, vdaa..vdaz, vdba..vdbz, ...
func DevPath(idx int) string {
const letters = 26
if idx < letters {
return fmt.Sprintf("/dev/vd%c", 'a'+idx)
}
return fmt.Sprintf("/dev/vd%c%c", 'a'+(idx/letters)-1, 'a'+idx%letters)
}

// EnsureVmlinux decompresses kernelPath if needed and returns the ELF path.
func EnsureVmlinux(kernelPath string) (string, error) {
elfMagic := []byte{0x7f, 'E', 'L', 'F'}
Expand Down Expand Up @@ -163,12 +172,3 @@ func buildCmdline(storageConfigs []*types.StorageConfig, networkConfigs []*types
networkConfigs, vmName, dnsServers,
)
}

// DevPath maps idx to vda..vdz, vdaa..vdaz, vdba..vdbz, ...
func DevPath(idx int) string {
const letters = 26
if idx < letters {
return fmt.Sprintf("/dev/vd%c", 'a'+idx)
}
return fmt.Sprintf("/dev/vd%c%c", 'a'+(idx/letters)-1, 'a'+idx%letters)
}
2 changes: 1 addition & 1 deletion hypervisor/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (b *Backend) ResolveRefs(ctx context.Context, refs []string) ([]string, err
})
}

// LoadRecord returns a value-copy of the VMRecord.
// LoadRecord returns a shallow value-copy; pointer/slice/map fields still alias the live record. Treat as read-only outside DB transactions.
func (b *Backend) LoadRecord(ctx context.Context, id string) (VMRecord, error) {
var rec VMRecord
return rec, b.DB.With(ctx, func(idx *VMIndex) error {
Expand Down
3 changes: 3 additions & 0 deletions hypervisor/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ func PreflightRestore(srcDir, rootDir, runDir string, rec *VMRecord, integrity f
func CloneStorageConfigs(storageConfigs []*types.StorageConfig) []*types.StorageConfig {
out := make([]*types.StorageConfig, 0, len(storageConfigs))
for _, sc := range storageConfigs {
if sc == nil {
continue
}
cp := *sc
out = append(out, &cp)
}
Expand Down
21 changes: 9 additions & 12 deletions hypervisor/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/cocoonstack/cocoon/utils"
)

const socketProbeTimeout = 2 * time.Second
const socketProbeTimeout = 500 * time.Millisecond

// WithRunningVM calls fn if rec still points to a live VM process.
func (b *Backend) WithRunningVM(ctx context.Context, rec *VMRecord, fn func(pid int) error) error {
Expand Down Expand Up @@ -88,11 +88,14 @@ func (b *Backend) WithPausedVM(ctx context.Context, rec *VMRecord, pause, resume
})
}

// UpdateStates batch-updates State; transitions to Stopped close the compute interval when one is open (covers Error→Stopped from rm --force or recovery stop). Error transitions leave StoppedAt nil because many MarkError paths can't prove the process is dead.
// UpdateStates flips ids to Stopped or Error and emits compute.stop on Running→Stopped (Error paths can't prove the process is dead so the interval stays open until a confirmed-dead helper closes it). To open a fresh interval, use BatchMarkStarted — UpdateStates intentionally rejects Running to avoid silent ledger drift.
func (b *Backend) UpdateStates(ctx context.Context, ids []string, state types.VMState) error {
if len(ids) == 0 {
return nil
}
if state == types.VMStateRunning {
return fmt.Errorf("UpdateStates(Running) not allowed; use BatchMarkStarted")
}
now := time.Now()
var stopped []metering.Entry
if err := b.DB.Update(ctx, func(idx *VMIndex) error {
Expand All @@ -103,15 +106,9 @@ func (b *Backend) UpdateStates(ctx context.Context, ids []string, state types.VM
}
r.State = state
r.UpdatedAt = now
switch state {
case types.VMStateRunning:
r.StartedAt = &now
r.StoppedAt = nil
case types.VMStateStopped:
if hasOpenComputeInterval(r) {
r.StoppedAt = &now
stopped = append(stopped, b.makeEntry(metering.KindVMComputeStop, id, metering.ReasonStopUser, shapeFromConfig(r.Config), now))
}
if state == types.VMStateStopped && hasOpenComputeInterval(r) {
r.StoppedAt = &now
stopped = append(stopped, b.makeEntry(metering.KindVMComputeStop, id, metering.ReasonStopUser, shapeFromConfig(r.Config), now))
}
}
return nil
Expand All @@ -129,7 +126,7 @@ func (b *Backend) MarkError(ctx context.Context, id string) {
}
}

// BatchMarkStarted flips ids to VMStateRunning; State==Running entrants are stale-running (close stop-crash, then open fresh).
// BatchMarkStarted flips ids to VMStateRunning; entrants with an open compute interval are stale-running (close stop-crash, then open fresh).
func (b *Backend) BatchMarkStarted(ctx context.Context, ids []string) error {
if len(ids) == 0 {
return nil
Expand Down
28 changes: 19 additions & 9 deletions hypervisor/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,10 @@ func TestUpdateStatesEmitsOnlyOnRunningToStopped(t *testing.T) {
t.Errorf("Created→Stopped emitted %d; want 0", len(got))
}

if err := b.UpdateStates(ctx, []string{"vm1"}, types.VMStateRunning); err != nil {
t.Fatalf("UpdateStates(running): %v", err)
}
if got := rec.Entries(); len(got) != 0 {
t.Errorf("Stopped→Running emitted %d; want 0", len(got))
if err := b.BatchMarkStarted(ctx, []string{"vm1"}); err != nil {
t.Fatalf("BatchMarkStarted: %v", err)
}
rec.Reset()

if err := b.UpdateStates(ctx, []string{"vm1"}, types.VMStateStopped); err != nil {
t.Fatalf("UpdateStates(stopped): %v", err)
Expand All @@ -141,8 +139,8 @@ func TestUpdateStatesEmitsOnlyOnRunningToStopped(t *testing.T) {
t.Fatalf("Running→Stopped: got %+v, want one compute.stop reason=user", entries)
}

if err := b.UpdateStates(ctx, []string{"vm1"}, types.VMStateRunning); err != nil {
t.Fatalf("UpdateStates(running again): %v", err)
if err := b.BatchMarkStarted(ctx, []string{"vm1"}); err != nil {
t.Fatalf("BatchMarkStarted (relaunch): %v", err)
}
rec.Reset()
if err := b.UpdateStates(ctx, []string{"vm1"}, types.VMStateError); err != nil {
Expand All @@ -153,6 +151,15 @@ func TestUpdateStatesEmitsOnlyOnRunningToStopped(t *testing.T) {
}
}

func TestUpdateStatesRunningIsRejected(t *testing.T) {
b, _ := newMeteringTestBackend(t)
ctx := t.Context()
seedVMRecord(t, b, "vm1", 1, 1<<30, 10<<30, false)
if err := b.UpdateStates(ctx, []string{"vm1"}, types.VMStateRunning); err == nil {
t.Fatal("UpdateStates(Running) must return an error to steer callers to BatchMarkStarted")
}
}

func TestPrepareStartClosesIntervalAfterMarkError(t *testing.T) {
// Running→Error must leave the interval open (UpdateStates(Error) doesn't write StoppedAt). The next PrepareStart confirms the process is dead and closes the interval.
b, rec := newMeteringTestBackend(t)
Expand Down Expand Up @@ -488,8 +495,11 @@ func TestDeleteAfterErrorEmitsOnlyStorageStop(t *testing.T) {
b, rec := newMeteringTestBackend(t)
ctx := t.Context()
seedVMRecord(t, b, "vm1", 2, 2<<30, 20<<30, true)
if err := b.UpdateStates(ctx, []string{"vm1"}, types.VMStateRunning); err != nil {
t.Fatalf("UpdateStates(running): %v", err)
if err := b.BatchMarkStarted(ctx, []string{"vm1"}); err != nil {
t.Fatalf("BatchMarkStarted: %v", err)
}
if err := b.UpdateStates(ctx, []string{"vm1"}, types.VMStateStopped); err != nil {
t.Fatalf("UpdateStates(stopped): %v", err)
}
if err := b.UpdateStates(ctx, []string{"vm1"}, types.VMStateError); err != nil {
t.Fatalf("UpdateStates(error): %v", err)
Expand Down
3 changes: 3 additions & 0 deletions images/cloudimg/cloudimg.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type CloudImg struct {
}

func New(ctx context.Context, conf *config.Config) (*CloudImg, error) {
if conf == nil {
return nil, fmt.Errorf("config is nil")
}
cfg := NewConfig(conf)
if err := cfg.EnsureDirs(); err != nil {
return nil, fmt.Errorf("ensure dirs: %w", err)
Expand Down
26 changes: 13 additions & 13 deletions metadata/fat12.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ const (
mediaDesc = 0xF8
)

// CreateFAT12 streams a 1 MiB FAT12 image with VFAT long-filename support to w.
// label is the volume label (e.g. "CIDATA"); files maps filename → content.
func CreateFAT12(w io.Writer, label string, files map[string][]byte) error {
b := newFAT12Builder(label)

for _, name := range slices.Sorted(maps.Keys(files)) {
if err := b.addFile(name, files[name]); err != nil {
return err
}
}
return b.writeTo(w)
}

type dataEntry struct {
data []byte
numClusters int
Expand Down Expand Up @@ -189,19 +202,6 @@ func (b *fat12Builder) makeBootSector() []byte {
return boot
}

// CreateFAT12 streams a 1 MiB FAT12 image with VFAT long-filename support to w.
// label is the volume label (e.g. "CIDATA"); files maps filename → content.
func CreateFAT12(w io.Writer, label string, files map[string][]byte) error {
b := newFAT12Builder(label)

for _, name := range slices.Sorted(maps.Keys(files)) {
if err := b.addFile(name, files[name]); err != nil {
return err
}
}
return b.writeTo(w)
}

// setFATEntry writes a 12-bit value into the FAT at the given cluster index.
func setFATEntry(fat []byte, cluster int, val uint16) {
off := cluster + cluster/2 //nolint:mnd
Expand Down
Loading