From d9374a6aec53999e2e565f5778cc49f472a898f2 Mon Sep 17 00:00:00 2001 From: fatelei Date: Wed, 17 Dec 2025 17:25:40 +0800 Subject: [PATCH] feat: add correct qemu inspect status method Signed-off-by: fatelei --- pkg/driver/qemu/qemu_driver.go | 23 ++++- pkg/driver/qemu/qemu_driver_test.go | 146 ++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 pkg/driver/qemu/qemu_driver_test.go diff --git a/pkg/driver/qemu/qemu_driver.go b/pkg/driver/qemu/qemu_driver.go index 3cb7bed29d7..4cec6d70062 100644 --- a/pkg/driver/qemu/qemu_driver.go +++ b/pkg/driver/qemu/qemu_driver.go @@ -37,6 +37,7 @@ import ( "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/ptr" "github.com/lima-vm/lima/v2/pkg/reflectutil" + "github.com/lima-vm/lima/v2/pkg/store" "github.com/lima-vm/lima/v2/pkg/version/versionutil" ) @@ -696,7 +697,27 @@ func (l *LimaQemuDriver) SSHAddress(_ context.Context) (string, error) { return "127.0.0.1", nil } -func (l *LimaQemuDriver) InspectStatus(_ context.Context, _ *limatype.Instance) string { +func (l *LimaQemuDriver) InspectStatus(_ context.Context, instance *limatype.Instance) string { + if instance == nil || instance.Dir == "" { + return limatype.StatusBroken + } + + qemuPIDPath := filepath.Join(instance.Dir, filenames.PIDFile(limatype.QEMU)) + if _, statErr := os.Stat(qemuPIDPath); statErr != nil { + if errors.Is(statErr, os.ErrNotExist) { + return "" + } + return limatype.StatusBroken + } + pid, err := store.ReadPIDFile(qemuPIDPath) + if err != nil { + return limatype.StatusBroken + } + + if pid == 0 { + return limatype.StatusBroken + } + return "" } diff --git a/pkg/driver/qemu/qemu_driver_test.go b/pkg/driver/qemu/qemu_driver_test.go new file mode 100644 index 00000000000..6e25c3ac7f2 --- /dev/null +++ b/pkg/driver/qemu/qemu_driver_test.go @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package qemu + +import ( + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "testing" + + "gotest.tools/v3/assert" + + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" +) + +func TestInspectStatus(t *testing.T) { + tests := []struct { + name string + setupInstance func(*limatype.Instance) + expectedStatus string + }{ + { + name: "nil instance", + setupInstance: func(inst *limatype.Instance) { + *inst = limatype.Instance{} + }, + expectedStatus: limatype.StatusBroken, + }, + { + name: "empty directory", + setupInstance: func(inst *limatype.Instance) { + *inst = limatype.Instance{ + Dir: "", + Config: &limatype.LimaYAML{}, + } + }, + expectedStatus: limatype.StatusBroken, + }, + { + name: "no PID file", + setupInstance: func(inst *limatype.Instance) { + tempDir := t.TempDir() + *inst = limatype.Instance{ + Dir: tempDir, + Config: &limatype.LimaYAML{}, + } + }, + expectedStatus: "", // fallback should handle this + }, + { + name: "PID file with valid process", + setupInstance: func(inst *limatype.Instance) { + tempDir := t.TempDir() + pidFile := filepath.Join(tempDir, filenames.PIDFile(limatype.QEMU)) + + // Create a PID file with the current test process's PID (guaranteed to be running) + pidStr := strconv.Itoa(os.Getpid()) + if err := os.WriteFile(pidFile, []byte(pidStr), 0o644); err != nil { + assert.NilError(t, err) + } + + *inst = limatype.Instance{ + Dir: tempDir, + Config: &limatype.LimaYAML{}, + } + }, + expectedStatus: "", // fallback should handle this + }, + { + name: "PID file with dead process", + setupInstance: func(inst *limatype.Instance) { + tempDir := t.TempDir() + pidFile := filepath.Join(tempDir, filenames.PIDFile(limatype.QEMU)) + + // Create a short-lived child process and use its PID after it exits to ensure it's dead + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.CommandContext(t.Context(), "cmd", "/C", "exit", "0") + } else { + // Use a longer-lived process to avoid immediate PID reuse races + cmd = exec.CommandContext(t.Context(), "sh", "-c", "sleep 5") + } + if err := cmd.Start(); err != nil { + assert.NilError(t, err) + } + pid := cmd.Process.Pid + if err := os.WriteFile(pidFile, []byte(strconv.Itoa(pid)), 0o644); err != nil { + assert.NilError(t, err) + } + // Ensure the process is dead when InspectStatus reads the PID file + if runtime.GOOS != "windows" { + _ = cmd.Process.Kill() + _ = cmd.Wait() + } + + *inst = limatype.Instance{ + Dir: tempDir, + Config: &limatype.LimaYAML{}, + } + }, + expectedStatus: func() string { + // On Windows, ReadPIDFile returns the PID without checking liveness, so InspectStatus returns "". + if runtime.GOOS == "windows" { + return "" + } + return limatype.StatusBroken + }(), + }, + { + name: "PID file with invalid content", + setupInstance: func(inst *limatype.Instance) { + tempDir := t.TempDir() + pidFile := filepath.Join(tempDir, filenames.PIDFile(limatype.QEMU)) + + // Create a PID file with invalid content (non-numeric) + if err := os.WriteFile(pidFile, []byte("invalid"), 0o644); err != nil { + assert.NilError(t, err) + } + + *inst = limatype.Instance{ + Dir: tempDir, + Config: &limatype.LimaYAML{}, + } + }, + expectedStatus: limatype.StatusBroken, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var inst limatype.Instance + tt.setupInstance(&inst) + + driver := &LimaQemuDriver{} + status := driver.InspectStatus(t.Context(), &inst) + + if status != tt.expectedStatus { + t.Errorf("InspectStatus() = %v, want %v", status, tt.expectedStatus) + } + }) + } +}