daemon split (4/5): extract *VMService service
Phase 4 of the daemon god-struct refactor. VM lifecycle, create-op
registry, handle cache, disk provisioning, stats polling, ports
query, and the per-VM lock set all move off *Daemon onto *VMService.
Daemon keeps thin forwarders only for FindVM / TouchVM (dispatch
surface) and is otherwise out of VM lifecycle. Lazy-init via
d.vmSvc() mirrors the earlier services so test literals like
\`&Daemon{store: db, runner: r}\` still get a functional service
without spelling one out.
Three small cleanups along the way:
* preflight helpers (validateStartPrereqs / addBaseStartPrereqs
/ addBaseStartCommandPrereqs / validateWorkDiskResizePrereqs)
move with the VM methods that call them.
* cleanupRuntime / rebuildDNS move to *VMService, with
HostNetwork primitives (findFirecrackerPID, cleanupDMSnapshot,
killVMProcess, releaseTap, waitForExit, sendCtrlAltDel)
reached through s.net instead of the hostNet() facade.
* vsockAgentBinary becomes a package-level function so both
*Daemon (doctor) and *VMService (preflight) call one entry
point instead of each owning a forwarder method.
WorkspaceService's peer deps switch from eager method values to
closures — vmSvc() constructs VMService with WorkspaceService as a
peer, so resolving d.vmSvc().FindVM at construction time recursed
through workspaceSvc() → vmSvc(). Closures defer the lookup to call
time.
Pure code motion: build + unit tests green, lint clean. No RPC
surface or lock-ordering changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c0d456e734
commit
466a7c30c4
23 changed files with 655 additions and 463 deletions
|
|
@ -27,8 +27,8 @@ import (
|
|||
// won.
|
||||
// 3. Boot. Only the per-VM lock is held — parallel creates against
|
||||
// different VMs fully overlap.
|
||||
func (d *Daemon) CreateVM(ctx context.Context, params api.VMCreateParams) (vm model.VMRecord, err error) {
|
||||
op := d.beginOperation("vm.create")
|
||||
func (s *VMService) CreateVM(ctx context.Context, params api.VMCreateParams) (vm model.VMRecord, err error) {
|
||||
op := s.beginOperation("vm.create")
|
||||
defer func() {
|
||||
if err != nil {
|
||||
op.fail(err)
|
||||
|
|
@ -45,10 +45,10 @@ func (d *Daemon) CreateVM(ctx context.Context, params api.VMCreateParams) (vm mo
|
|||
|
||||
imageName := params.ImageName
|
||||
if imageName == "" {
|
||||
imageName = d.config.DefaultImageName
|
||||
imageName = s.config.DefaultImageName
|
||||
}
|
||||
vmCreateStage(ctx, "resolve_image", "resolving image")
|
||||
image, err := d.findOrAutoPullImage(ctx, imageName)
|
||||
image, err := s.findOrAutoPullImage(ctx, imageName)
|
||||
if err != nil {
|
||||
return model.VMRecord{}, err
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ func (d *Daemon) CreateVM(ctx context.Context, params api.VMCreateParams) (vm mo
|
|||
NATEnabled: params.NATEnabled,
|
||||
}
|
||||
|
||||
vm, err = d.reserveVM(ctx, strings.TrimSpace(params.Name), image, spec)
|
||||
vm, err = s.reserveVM(ctx, strings.TrimSpace(params.Name), image, spec)
|
||||
if err != nil {
|
||||
return model.VMRecord{}, err
|
||||
}
|
||||
|
|
@ -85,31 +85,31 @@ func (d *Daemon) CreateVM(ctx context.Context, params api.VMCreateParams) (vm mo
|
|||
vmCreateBindVM(ctx, vm)
|
||||
vmCreateStage(ctx, "reserve_vm", fmt.Sprintf("allocated %s (%s)", vm.Name, vm.Runtime.GuestIP))
|
||||
|
||||
unlockVM := d.lockVMID(vm.ID)
|
||||
unlockVM := s.lockVMID(vm.ID)
|
||||
defer unlockVM()
|
||||
|
||||
if params.NoStart {
|
||||
vm.State = model.VMStateStopped
|
||||
vm.Runtime.State = model.VMStateStopped
|
||||
if err := d.store.UpsertVM(ctx, vm); err != nil {
|
||||
if err := s.store.UpsertVM(ctx, vm); err != nil {
|
||||
return model.VMRecord{}, err
|
||||
}
|
||||
return vm, nil
|
||||
}
|
||||
return d.startVMLocked(ctx, vm, image)
|
||||
return s.startVMLocked(ctx, vm, image)
|
||||
}
|
||||
|
||||
// reserveVM holds createVMMu only long enough to verify the name is
|
||||
// free, allocate a guest IP from the store, and persist the "created"
|
||||
// reservation row. Everything else (image resolution upstream, boot
|
||||
// downstream) runs outside this lock.
|
||||
func (d *Daemon) reserveVM(ctx context.Context, requestedName string, image model.Image, spec model.VMSpec) (model.VMRecord, error) {
|
||||
d.createVMMu.Lock()
|
||||
defer d.createVMMu.Unlock()
|
||||
func (s *VMService) reserveVM(ctx context.Context, requestedName string, image model.Image, spec model.VMSpec) (model.VMRecord, error) {
|
||||
s.createVMMu.Lock()
|
||||
defer s.createVMMu.Unlock()
|
||||
|
||||
name := requestedName
|
||||
if name == "" {
|
||||
generated, err := d.generateName(ctx)
|
||||
generated, err := s.generateName(ctx)
|
||||
if err != nil {
|
||||
return model.VMRecord{}, err
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ func (d *Daemon) reserveVM(ctx context.Context, requestedName string, image mode
|
|||
// Exact-name lookup. Using FindVM here would also match a new name
|
||||
// that merely prefixes some existing VM's id or another VM's name,
|
||||
// falsely rejecting perfectly valid names.
|
||||
if _, err := d.store.GetVMByName(ctx, name); err == nil {
|
||||
if _, err := s.store.GetVMByName(ctx, name); err == nil {
|
||||
return model.VMRecord{}, fmt.Errorf("vm name already exists: %s", name)
|
||||
} else if !errors.Is(err, sql.ErrNoRows) {
|
||||
return model.VMRecord{}, err
|
||||
|
|
@ -128,11 +128,11 @@ func (d *Daemon) reserveVM(ctx context.Context, requestedName string, image mode
|
|||
if err != nil {
|
||||
return model.VMRecord{}, err
|
||||
}
|
||||
guestIP, err := d.store.NextGuestIP(ctx, bridgePrefix(d.config.BridgeIP))
|
||||
guestIP, err := s.store.NextGuestIP(ctx, bridgePrefix(s.config.BridgeIP))
|
||||
if err != nil {
|
||||
return model.VMRecord{}, err
|
||||
}
|
||||
vmDir := filepath.Join(d.layout.VMsDir, id)
|
||||
vmDir := filepath.Join(s.layout.VMsDir, id)
|
||||
if err := os.MkdirAll(vmDir, 0o755); err != nil {
|
||||
return model.VMRecord{}, err
|
||||
}
|
||||
|
|
@ -155,7 +155,7 @@ func (d *Daemon) reserveVM(ctx context.Context, requestedName string, image mode
|
|||
GuestIP: guestIP,
|
||||
DNSName: vmdns.RecordName(name),
|
||||
VMDir: vmDir,
|
||||
VSockPath: defaultVSockPath(d.layout.RuntimeDir, id),
|
||||
VSockPath: defaultVSockPath(s.layout.RuntimeDir, id),
|
||||
VSockCID: vsockCID,
|
||||
SystemOverlay: filepath.Join(vmDir, "system.cow"),
|
||||
WorkDiskPath: filepath.Join(vmDir, "root.ext4"),
|
||||
|
|
@ -163,7 +163,7 @@ func (d *Daemon) reserveVM(ctx context.Context, requestedName string, image mode
|
|||
MetricsPath: filepath.Join(vmDir, "metrics.json"),
|
||||
},
|
||||
}
|
||||
if err := d.store.UpsertVM(ctx, vm); err != nil {
|
||||
if err := s.store.UpsertVM(ctx, vm); err != nil {
|
||||
return model.VMRecord{}, err
|
||||
}
|
||||
return vm, nil
|
||||
|
|
@ -174,8 +174,8 @@ func (d *Daemon) reserveVM(ctx context.Context, requestedName string, image mode
|
|||
// catalog, it auto-pulls the bundle so `vm create --image foo` (and
|
||||
// therefore `vm run`) works on a fresh host without the user having
|
||||
// to run `image pull` first.
|
||||
func (d *Daemon) findOrAutoPullImage(ctx context.Context, idOrName string) (model.Image, error) {
|
||||
image, err := d.imageSvc().FindImage(ctx, idOrName)
|
||||
func (s *VMService) findOrAutoPullImage(ctx context.Context, idOrName string) (model.Image, error) {
|
||||
image, err := s.img.FindImage(ctx, idOrName)
|
||||
if err == nil {
|
||||
return image, nil
|
||||
}
|
||||
|
|
@ -189,8 +189,8 @@ func (d *Daemon) findOrAutoPullImage(ctx context.Context, idOrName string) (mode
|
|||
return model.Image{}, err
|
||||
}
|
||||
vmCreateStage(ctx, "auto_pull_image", fmt.Sprintf("pulling %s from image catalog", entry.Name))
|
||||
if _, pullErr := d.imageSvc().PullImage(ctx, api.ImagePullParams{Ref: entry.Name}); pullErr != nil {
|
||||
if _, pullErr := s.img.PullImage(ctx, api.ImagePullParams{Ref: entry.Name}); pullErr != nil {
|
||||
return model.Image{}, fmt.Errorf("auto-pull image %q: %w", entry.Name, pullErr)
|
||||
}
|
||||
return d.imageSvc().FindImage(ctx, idOrName)
|
||||
return s.img.FindImage(ctx, idOrName)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue