Skip to content

Commit 2cfd55d

Browse files
committed
Extend delete shutdown grace period
1 parent e47fd30 commit 2cfd55d

2 files changed

Lines changed: 111 additions & 1 deletion

File tree

‎lib/instances/delete.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"go.opentelemetry.io/otel/attribute"
1616
)
1717

18-
const deleteGracefulShutdownTimeout = 2
18+
const deleteGracefulShutdownTimeout = 5
1919

2020
type deleteInstanceOptions struct {
2121
skipGracefulShutdown bool

‎lib/instances/delete_test.go‎

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,124 @@
11
package instances
22

33
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"os"
48
"os/exec"
9+
"path/filepath"
10+
"sync"
11+
"syscall"
512
"testing"
613
"time"
714

15+
"github.com/kernel/hypeman/lib/guest"
16+
"github.com/kernel/hypeman/lib/hypervisor"
817
"github.com/stretchr/testify/assert"
918
"github.com/stretchr/testify/require"
19+
"google.golang.org/grpc"
20+
"google.golang.org/grpc/test/bufconn"
1021
)
1122

23+
var deleteTestVsockListeners sync.Map
24+
25+
func init() {
26+
hypervisor.RegisterVsockDialerFactory(lifecycleNoopHypervisorType, func(vsockSocket string, _ int64) hypervisor.VsockDialer {
27+
return deleteTestVsockDialer{key: vsockSocket}
28+
})
29+
}
30+
31+
type deleteTestVsockDialer struct {
32+
key string
33+
}
34+
35+
func (d deleteTestVsockDialer) DialVsock(ctx context.Context, _ int) (net.Conn, error) {
36+
listener, ok := deleteTestVsockListeners.Load(d.key)
37+
if !ok {
38+
return nil, fmt.Errorf("missing test vsock listener for %s", d.key)
39+
}
40+
return listener.(*bufconn.Listener).DialContext(ctx)
41+
}
42+
43+
func (d deleteTestVsockDialer) Key() string {
44+
return "delete-test:" + d.key
45+
}
46+
47+
type delayedShutdownGuestServer struct {
48+
guest.UnimplementedGuestServiceServer
49+
pid int
50+
delay time.Duration
51+
markerPath string
52+
once sync.Once
53+
}
54+
55+
func (s *delayedShutdownGuestServer) Shutdown(context.Context, *guest.ShutdownRequest) (*guest.ShutdownResponse, error) {
56+
s.once.Do(func() {
57+
go func() {
58+
time.Sleep(s.delay)
59+
if !processExists(s.pid) {
60+
return
61+
}
62+
if err := os.WriteFile(s.markerPath, []byte("graceful"), 0o644); err != nil {
63+
return
64+
}
65+
_ = syscall.Kill(s.pid, syscall.SIGTERM)
66+
}()
67+
})
68+
return &guest.ShutdownResponse{}, nil
69+
}
70+
71+
func TestDeleteInstanceAllowsGracefulShutdownBeyondTwoSeconds(t *testing.T) {
72+
mgr, id := newLifecycleNoopManagerWithInstance(t, StateRunning, time.Now().UTC())
73+
markerPath := filepath.Join(t.TempDir(), "graceful-shutdown")
74+
75+
cmd := exec.Command("sleep", "30")
76+
require.NoError(t, cmd.Start())
77+
t.Cleanup(func() {
78+
_ = cmd.Process.Kill()
79+
_, _ = cmd.Process.Wait()
80+
})
81+
82+
shutdownDelay := 2200 * time.Millisecond
83+
shutdownServer := &delayedShutdownGuestServer{
84+
pid: cmd.Process.Pid,
85+
delay: shutdownDelay,
86+
markerPath: markerPath,
87+
}
88+
listener := bufconn.Listen(1024 * 1024)
89+
grpcServer := grpc.NewServer()
90+
guest.RegisterGuestServiceServer(grpcServer, shutdownServer)
91+
go func() {
92+
_ = grpcServer.Serve(listener)
93+
}()
94+
t.Cleanup(func() {
95+
grpcServer.Stop()
96+
_ = listener.Close()
97+
})
98+
99+
meta, err := mgr.loadMetadata(id)
100+
require.NoError(t, err)
101+
meta.HypervisorPID = &shutdownServer.pid
102+
meta.StopTimeout = 10
103+
meta.VsockSocket = filepath.Join(meta.DataDir, "test-vsock.sock")
104+
deleteTestVsockListeners.Store(meta.VsockSocket, listener)
105+
t.Cleanup(func() {
106+
deleteTestVsockListeners.Delete(meta.VsockSocket)
107+
guest.CloseConn(deleteTestVsockDialer{key: meta.VsockSocket}.Key())
108+
})
109+
require.NoError(t, mgr.saveMetadata(meta))
110+
111+
start := time.Now()
112+
require.NoError(t, mgr.deleteInstance(context.Background(), id))
113+
elapsed := time.Since(start)
114+
115+
marker, err := os.ReadFile(markerPath)
116+
require.NoError(t, err, "shutdown should finish before the delete timeout")
117+
assert.Equal(t, "graceful", string(marker))
118+
assert.GreaterOrEqual(t, elapsed, shutdownDelay)
119+
assert.Less(t, elapsed, time.Duration(deleteGracefulShutdownTimeout)*time.Second)
120+
}
121+
12122
func TestWaitForProcessExit_ReapsZombieChild(t *testing.T) {
13123
t.Parallel()
14124
cmd := exec.Command("sh", "-c", "exit 0")

0 commit comments

Comments
 (0)