Skip to content

Commit fa369c1

Browse files
committed
Handle qemu pool backend switches and version policy
1 parent 8cc10e5 commit fa369c1

5 files changed

Lines changed: 58 additions & 25 deletions

File tree

‎lib/hypervisor/qemu/pool.go‎

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package qemu
22

33
import (
4-
"fmt"
54
"sync"
65

76
"github.com/kernel/hypeman/lib/hypervisor"
@@ -28,11 +27,11 @@ func GetOrCreateForType(socketPath string, hypervisorType hypervisor.Type) (*QEM
2827
// Try read lock first for existing connection
2928
clientPool.RLock()
3029
ifclient, ok:=clientPool.clients[socketPath]; ok {
31-
clientPool.RUnlock()
32-
ifclient.hypervisorType!=hypervisorType {
33-
returnnil, poolTypeMismatchError(socketPath, client.hypervisorType, hypervisorType)
30+
ifclient.hypervisorType==hypervisorType {
31+
clientPool.RUnlock()
32+
returnclient, nil
3433
}
35-
returnclient, nil
34+
// Backend identity changed for this socket path. Recreate under write lock.
3635
}
3736
clientPool.RUnlock()
3837

@@ -42,10 +41,11 @@ func GetOrCreateForType(socketPath string, hypervisorType hypervisor.Type) (*QEM
4241

4342
// Double-check after acquiring write lock
4443
ifclient, ok:=clientPool.clients[socketPath]; ok {
45-
ifclient.hypervisorType!=hypervisorType {
46-
returnnil, poolTypeMismatchError(socketPath, client.hypervisorType, hypervisorType)
44+
ifclient.hypervisorType==hypervisorType {
45+
returnclient, nil
4746
}
48-
returnclient, nil
47+
// Stale pooled backend type for this socket path. Drop and reconnect.
48+
removeLocked(socketPath)
4949
}
5050

5151
// Create new client
@@ -58,20 +58,22 @@ func GetOrCreateForType(socketPath string, hypervisorType hypervisor.Type) (*QEM
5858
returnclient, nil
5959
}
6060

61-
funcpoolTypeMismatchError(socketPathstring, cached, requested hypervisor.Type) error {
62-
returnfmt.Errorf("QEMU client for %s is pooled as hypervisor %s, not %s", socketPath, cached, requested)
63-
}
64-
6561
// Remove closes and removes a client from the pool.
6662
// Called automatically on errors to allow fresh reconnection.
6763
// Close is done asynchronously to avoid blocking if the connection is in a bad state.
6864
funcRemove(socketPathstring) {
6965
clientPool.Lock()
7066
deferclientPool.Unlock()
67+
removeLocked(socketPath)
68+
}
7169

70+
// removeLocked removes an entry while clientPool lock is held.
71+
funcremoveLocked(socketPathstring) {
7272
ifclient, ok:=clientPool.clients[socketPath]; ok {
7373
delete(clientPool.clients, socketPath)
7474
// Close asynchronously to avoid blocking on stuck connections
75-
goclient.client.Close()
75+
ifclient.client!=nil {
76+
goclient.client.Close()
77+
}
7678
}
7779
}

‎lib/hypervisor/qemu/pool_test.go‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/stretchr/testify/require"
88
)
99

10-
funcTestGetOrCreateForTypeRejectsCachedBackendMismatch(t*testing.T) {
10+
funcTestGetOrCreateForTypeEvictsCachedBackendMismatch(t*testing.T) {
1111
socketPath:=t.TempDir() +"/qemu.sock"
1212
clientPool.Lock()
1313
clientPool.clients[socketPath] =&QEMU{socketPath: socketPath, hypervisorType: hypervisor.TypeQEMU}
@@ -19,5 +19,9 @@ func TestGetOrCreateForTypeRejectsCachedBackendMismatch(t *testing.T) {
1919
})
2020

2121
_, err:=GetOrCreateForType(socketPath, hypervisor.TypeQEMUMicroVM)
22-
require.ErrorContains(t, err, "pooled as hypervisor qemu, not qemu-microvm")
22+
require.Error(t, err)
23+
clientPool.RLock()
24+
_, stillPooled:=clientPool.clients[socketPath]
25+
clientPool.RUnlock()
26+
require.False(t, stillPooled, "stale cached backend must be evicted after mismatch")
2327
}

‎lib/instances/create.go‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -748,13 +748,13 @@ func (m *manager) startAndBootVM(
748748
returnfmt.Errorf("get vm starter: %w", err)
749749
}
750750

751-
// qemu-microvm snapshots are tied to the binary that boots the VM. Refresh
752-
// metadata on every cold start so host upgrades do not leave the instance's
753-
// reported version pinned to its original creation time.
754-
ifstored.HypervisorType==hypervisor.TypeQEMUMicroVM {
751+
// Some hypervisors tie snapshot compatibility to the exact runtime binary.
752+
// Refresh metadata on cold start so host upgrades do not leave the stored
753+
// version pinned to original creation time.
754+
ifrefreshHypervisorVersionOnColdStart(stored.HypervisorType) {
755755
detectedVersion, err:=starter.GetVersion(m.paths)
756756
iferr!=nil {
757-
returnfmt.Errorf("get QEMU version for qemu-microvm start: %w", err)
757+
returnfmt.Errorf("get hypervisor version for %s start: %w", stored.HypervisorType, err)
758758
}
759759
stored.HypervisorVersion=detectedVersion
760760
}

‎lib/instances/hypervisor_version.go‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,46 @@ import (
88
"github.com/kernel/hypeman/lib/logger"
99
)
1010

11+
typehypervisorVersionPolicystruct {
12+
enforceExactInstalledVersionbool
13+
refreshOnColdStartbool
14+
}
15+
16+
varhypervisorVersionPoliciesByType=map[hypervisor.Type]hypervisorVersionPolicy{
17+
hypervisor.TypeQEMUMicroVM: {
18+
enforceExactInstalledVersion: true,
19+
refreshOnColdStart: true,
20+
},
21+
}
22+
23+
funcresolveHypervisorVersionPolicy(hvType hypervisor.Type) hypervisorVersionPolicy {
24+
ifpolicy, ok:=hypervisorVersionPoliciesByType[hvType]; ok {
25+
returnpolicy
26+
}
27+
returnhypervisorVersionPolicy{}
28+
}
29+
30+
funcenforceExactInstalledHypervisorVersion(hvType hypervisor.Type) bool {
31+
returnresolveHypervisorVersionPolicy(hvType).enforceExactInstalledVersion
32+
}
33+
34+
funcrefreshHypervisorVersionOnColdStart(hvType hypervisor.Type) bool {
35+
returnresolveHypervisorVersionPolicy(hvType).refreshOnColdStart
36+
}
37+
1138
func (m*manager) resolveCreateHypervisorVersion(
1239
ctx context.Context,
1340
starter hypervisor.VMStarter,
1441
hvType hypervisor.Type,
1542
requestedstring,
1643
) (string, error) {
17-
ifhvType==hypervisor.TypeQEMUMicroVM {
44+
ifenforceExactInstalledHypervisorVersion(hvType) {
1845
detected, err:=starter.GetVersion(m.paths)
1946
iferr!=nil {
20-
return"", fmt.Errorf("get QEMU version for qemu-microvm: %w", err)
47+
return"", fmt.Errorf("get installed hypervisor version for %s: %w", hvType, err)
2148
}
2249
ifrequested!=""&&requested!=detected {
23-
return"", fmt.Errorf("%w: requested qemu-microvm version %q does not match installed QEMU %q", ErrInvalidRequest, requested, detected)
50+
return"", fmt.Errorf("%w: requested %s hypervisor version %q does not match installed version %q", ErrInvalidRequest, hvType, requested, detected)
2451
}
2552
returndetected, nil
2653
}

‎lib/instances/snapshot.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,8 @@ func (m *manager) prepareSnapshotTarget(ctx context.Context, source StoredMetada
532532
iftarget!=source.HypervisorType {
533533
version, err=starter.GetVersion(m.paths)
534534
iferr!=nil {
535-
iftarget==hypervisor.TypeQEMUMicroVM {
536-
returnnil, "", fmt.Errorf("get QEMU version for qemu-microvm snapshot target: %w", err)
535+
ifenforceExactInstalledHypervisorVersion(target) {
536+
returnnil, "", fmt.Errorf("get installed hypervisor version for %s snapshot target: %w", target, err)
537537
}
538538
logger.FromContext(ctx).WarnContext(ctx, "failed to get hypervisor version", "hypervisor", target, "error", err)
539539
version="unknown"

0 commit comments

Comments
 (0)