mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 17:11:30 +00:00
fix(installer): disable os-prober to prevent update-grub hang on multi-disk hardware
On N100 mini-PCs with eMMC + NVMe, update-grub's os-prober probes all block devices via the bind-mounted /dev, hanging indefinitely on the eMMC. GRUB_DISABLE_OS_PROBER=true skips os-prober in the chroot. Also apply modernize lint fixes to vm test files (atomic.Int32).
This commit is contained in:
@@ -574,6 +574,7 @@ GRUB_TIMEOUT=5
|
||||
GRUB_DISTRIBUTOR=Spinifex
|
||||
GRUB_CMDLINE_LINUX_DEFAULT=""
|
||||
GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200 systemd.show_status=1"
|
||||
GRUB_DISABLE_OS_PROBER=true
|
||||
`
|
||||
|
||||
if err := os.WriteFile(filepath.Join(mountRoot, "etc/default/grub"), []byte(grubDefault), 0o644); err != nil {
|
||||
|
||||
@@ -218,9 +218,9 @@ func TestQueryGuestDeviceMapWait(t *testing.T) {
|
||||
|
||||
t.Run("device present on first attempt makes no extra calls", func(t *testing.T) {
|
||||
sleeps := stubDeviceMapRetrySleep(t)
|
||||
var calls int32
|
||||
var calls atomic.Int32
|
||||
qmpClient, cancel := newMockQMPClient(t, func(cmd qmp.QMPCommand) map[string]any {
|
||||
atomic.AddInt32(&calls, 1)
|
||||
calls.Add(1)
|
||||
require.Equal(t, "query-block", cmd.Execute)
|
||||
return map[string]any{
|
||||
"return": []qmp.BlockDevice{
|
||||
@@ -235,15 +235,15 @@ func TestQueryGuestDeviceMapWait(t *testing.T) {
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, deviceMap, "vdisk-vol-new")
|
||||
assert.Equal(t, int32(1), atomic.LoadInt32(&calls), "exactly one query-block call expected")
|
||||
assert.Equal(t, int32(1), calls.Load(), "exactly one query-block call expected")
|
||||
assert.Empty(t, *sleeps, "no sleep should occur on first-attempt success")
|
||||
})
|
||||
|
||||
t.Run("device missing all 5 attempts invokes final probe and returns last result", func(t *testing.T) {
|
||||
sleeps := stubDeviceMapRetrySleep(t)
|
||||
var calls int32
|
||||
var calls atomic.Int32
|
||||
qmpClient, cancel := newMockQMPClient(t, func(cmd qmp.QMPCommand) map[string]any {
|
||||
atomic.AddInt32(&calls, 1)
|
||||
calls.Add(1)
|
||||
require.Equal(t, "query-block", cmd.Execute)
|
||||
return map[string]any{
|
||||
"return": []qmp.BlockDevice{
|
||||
@@ -256,7 +256,7 @@ func TestQueryGuestDeviceMapWait(t *testing.T) {
|
||||
deviceMap, err := queryGuestDeviceMapWait(qmpClient, "i-1", "vdisk-vol-missing")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int32(6), atomic.LoadInt32(&calls),
|
||||
assert.Equal(t, int32(6), calls.Load(),
|
||||
"5 retry attempts plus the final fall-through probe must all run")
|
||||
assert.Equal(t, []time.Duration{retryDelay, retryDelay, retryDelay, retryDelay}, *sleeps,
|
||||
"4 sleeps must occur between the 5 in-loop attempts (none after the final fall-through)")
|
||||
@@ -267,9 +267,9 @@ func TestQueryGuestDeviceMapWait(t *testing.T) {
|
||||
|
||||
t.Run("device appears on third attempt skips remaining retries", func(t *testing.T) {
|
||||
sleeps := stubDeviceMapRetrySleep(t)
|
||||
var calls int32
|
||||
var calls atomic.Int32
|
||||
qmpClient, cancel := newMockQMPClient(t, func(cmd qmp.QMPCommand) map[string]any {
|
||||
n := atomic.AddInt32(&calls, 1)
|
||||
n := calls.Add(1)
|
||||
require.Equal(t, "query-block", cmd.Execute)
|
||||
devices := []qmp.BlockDevice{
|
||||
bootDevice("os", "/machine/peripheral-anon/device[0]/virtio-backend"),
|
||||
@@ -285,7 +285,7 @@ func TestQueryGuestDeviceMapWait(t *testing.T) {
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, deviceMap, "vdisk-vol-late")
|
||||
assert.Equal(t, int32(3), atomic.LoadInt32(&calls),
|
||||
assert.Equal(t, int32(3), calls.Load(),
|
||||
"third-attempt success must not trigger a 4th or 5th probe")
|
||||
assert.Equal(t, []time.Duration{retryDelay, retryDelay}, *sleeps,
|
||||
"exactly 2 sleeps must occur (after attempts 1 and 2); none after the 3rd success")
|
||||
|
||||
+12
-12
@@ -663,9 +663,9 @@ func TestTryBlockdevDel(t *testing.T) {
|
||||
t.Run("success on first attempt logs no retry message", func(t *testing.T) {
|
||||
buf := captureSlog(t)
|
||||
|
||||
var attempts int32
|
||||
var attempts atomic.Int32
|
||||
qmpClient, cancel := newMockQMPClient(t, func(cmd qmp.QMPCommand) map[string]any {
|
||||
atomic.AddInt32(&attempts, 1)
|
||||
attempts.Add(1)
|
||||
return nil
|
||||
})
|
||||
defer cancel()
|
||||
@@ -673,7 +673,7 @@ func TestTryBlockdevDel(t *testing.T) {
|
||||
m := NewManagerWithDeps(Deps{})
|
||||
err := m.tryBlockdevDel(&VM{ID: "i-1", QMPClient: qmpClient}, "nbd-vol-1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int32(1), atomic.LoadInt32(&attempts))
|
||||
assert.Equal(t, int32(1), attempts.Load())
|
||||
assert.NotContains(t, buf.String(), "succeeded after retry",
|
||||
"first-attempt success must not log the retry-success line")
|
||||
})
|
||||
@@ -681,9 +681,9 @@ func TestTryBlockdevDel(t *testing.T) {
|
||||
t.Run("success on third attempt logs succeeded after retry", func(t *testing.T) {
|
||||
buf := captureSlog(t)
|
||||
|
||||
var attempts int32
|
||||
var attempts atomic.Int32
|
||||
qmpClient, cancel := newMockQMPClient(t, func(cmd qmp.QMPCommand) map[string]any {
|
||||
n := atomic.AddInt32(&attempts, 1)
|
||||
n := attempts.Add(1)
|
||||
if n < 3 {
|
||||
return map[string]any{"error": map[string]any{"class": "GenericError", "desc": inUseDesc}}
|
||||
}
|
||||
@@ -694,16 +694,16 @@ func TestTryBlockdevDel(t *testing.T) {
|
||||
m := NewManagerWithDeps(Deps{})
|
||||
err := m.tryBlockdevDel(&VM{ID: "i-1", QMPClient: qmpClient}, "nbd-vol-1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int32(3), atomic.LoadInt32(&attempts))
|
||||
assert.Equal(t, int32(3), attempts.Load())
|
||||
logs := buf.String()
|
||||
assert.Contains(t, logs, "succeeded after retry")
|
||||
assert.Contains(t, logs, "attempts=3")
|
||||
})
|
||||
|
||||
t.Run("non-in-use error on second attempt returns immediately", func(t *testing.T) {
|
||||
var attempts int32
|
||||
var attempts atomic.Int32
|
||||
qmpClient, cancel := newMockQMPClient(t, func(cmd qmp.QMPCommand) map[string]any {
|
||||
n := atomic.AddInt32(&attempts, 1)
|
||||
n := attempts.Add(1)
|
||||
switch n {
|
||||
case 1:
|
||||
return map[string]any{"error": map[string]any{"class": "GenericError", "desc": inUseDesc}}
|
||||
@@ -717,14 +717,14 @@ func TestTryBlockdevDel(t *testing.T) {
|
||||
err := m.tryBlockdevDel(&VM{ID: "i-1", QMPClient: qmpClient}, "nbd-vol-1")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "permission denied")
|
||||
assert.Equal(t, int32(2), atomic.LoadInt32(&attempts),
|
||||
assert.Equal(t, int32(2), attempts.Load(),
|
||||
"non-retryable error must stop the loop on the attempt that produced it")
|
||||
})
|
||||
|
||||
t.Run("twenty in-use errors return the last error", func(t *testing.T) {
|
||||
var attempts int32
|
||||
var attempts atomic.Int32
|
||||
qmpClient, cancel := newMockQMPClient(t, func(cmd qmp.QMPCommand) map[string]any {
|
||||
atomic.AddInt32(&attempts, 1)
|
||||
attempts.Add(1)
|
||||
return map[string]any{"error": map[string]any{"class": "GenericError", "desc": inUseDesc}}
|
||||
})
|
||||
defer cancel()
|
||||
@@ -734,7 +734,7 @@ func TestTryBlockdevDel(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
assert.True(t, isQMPNodeInUse(err),
|
||||
"exhaustion must return the last 'in use' error, not a wrapped or sentinel value")
|
||||
assert.Equal(t, int32(blockdevDelMaxAttempts), atomic.LoadInt32(&attempts),
|
||||
assert.Equal(t, int32(blockdevDelMaxAttempts), attempts.Load(),
|
||||
"retry must cap at blockdevDelMaxAttempts attempts")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user