-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathe2e_reload_test.go
More file actions
197 lines (170 loc) · 5 KB
/
Copy pathe2e_reload_test.go
File metadata and controls
197 lines (170 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// Copyright 2026 HAProxy Technologies LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"strings"
"syscall"
"testing"
"time"
)
func TestE2EHotReload(t *testing.T) {
// Services removed during reload get Stop()'d. The reap loop sees the exit
// and evaluates their exit action. Use on-success: ignore so the daemon
// doesn't shut down when the removed service's stop-signal death is reaped.
td := startDaemon(t, `
processes:
- name: svc-a
command: sleep
args: ["300"]
on-success: ignore
on-failure: shutdown
- name: svc-b
command: sleep
args: ["300"]
on-success: ignore
on-failure: ignore
`)
defer td.kill()
// Both should be running.
resp := td.sendCommand("status")
if !strings.Contains(resp, "svc-a") || !strings.Contains(resp, "svc-b") {
t.Fatalf("expected both services in list, got: %s", resp)
}
// Reload with svc-b removed and svc-c added.
td.updateConfig(`
processes:
- name: svc-a
command: sleep
args: ["300"]
on-success: ignore
on-failure: shutdown
- name: svc-c
command: sleep
args: ["300"]
on-success: ignore
on-failure: shutdown
`)
resp = td.sendCommand("reload")
if strings.Contains(resp, "error") {
t.Fatalf("reload failed: %s", resp)
}
time.Sleep(1 * time.Second)
// svc-a should still be running, svc-c should be running, svc-b should be gone.
resp = td.sendCommand("status")
if !strings.Contains(resp, "svc-a") {
t.Errorf("expected svc-a in list after reload, got: %s", resp)
}
if !strings.Contains(resp, "svc-c") {
t.Errorf("expected svc-c in list after reload, got: %s", resp)
}
if strings.Contains(resp, "svc-b") {
t.Errorf("svc-b should be removed after reload, got: %s", resp)
}
td.stop()
}
// TestE2EHotReloadOnFailureInPlace verifies that a reload which only changes
// `on-failure` mutates the running service's policy in place — no restart —
// and that the new policy applies to the next crash. Pre-reload semantics
// would shut the daemon down on the crash; post-reload semantics ignore it.
func TestE2EHotReloadOnFailureInPlace(t *testing.T) {
td := startDaemon(t, `
processes:
- name: keeper
command: sleep
args: ["300"]
on-failure: shutdown
- name: crasher
command: sleep
args: ["300"]
on-failure: shutdown
on-success: ignore
`)
defer td.kill()
resp := td.sendCommand("status crasher")
if !strings.Contains(resp, "running") {
t.Fatalf("expected crasher running, got: %s", resp)
}
// Reload with on-failure flipped to ignore. Same command + args so no
// restart is required; only policy fields mutate in place.
td.updateConfig(`
processes:
- name: keeper
command: sleep
args: ["300"]
on-failure: shutdown
- name: crasher
command: sleep
args: ["300"]
on-failure: ignore
on-success: ignore
`)
resp = td.sendCommand("reload")
if strings.Contains(resp, "error") {
t.Fatalf("reload failed: %s", resp)
}
// Confirm crasher was NOT restarted by the reload (in-place policy update).
resp = td.sendCommand("status crasher")
if !strings.Contains(resp, "running") {
t.Fatalf("crasher should still be running after policy-only reload, got: %s", resp)
}
// Crash the service via a real signal (not Stop()), so WasStopped() stays
// false and the reap loop dispatches the OnFailure action. With the new
// policy that action is Ignore; the daemon must stay alive.
resp = td.sendCommand("signal crasher SIGKILL")
if strings.Contains(resp, "error") {
t.Fatalf("signal failed: %s", resp)
}
time.Sleep(500 * time.Millisecond)
if !td.daemonAlive() {
t.Fatalf("daemon exited unexpectedly after reloaded on-failure: ignore should have suppressed shutdown")
}
resp = td.sendCommand("status crasher")
if !strings.Contains(resp, "stopped") {
t.Errorf("expected crasher stopped, got: %s", resp)
}
td.stop()
}
func TestE2EHotReloadSIGHUP(t *testing.T) {
td := startDaemon(t, `
processes:
- name: original
command: sleep
args: ["300"]
on-success: ignore
on-failure: shutdown
`)
defer td.kill()
// Add a new service to the config.
td.updateConfig(`
processes:
- name: original
command: sleep
args: ["300"]
on-success: ignore
on-failure: shutdown
- name: added
command: sleep
args: ["300"]
on-success: ignore
on-failure: shutdown
`)
// Trigger reload via SIGHUP.
td.signal(syscall.SIGHUP)
time.Sleep(2 * time.Second)
resp := td.sendCommand("status")
if !strings.Contains(resp, "added") {
t.Errorf("expected 'added' service after SIGHUP reload, got: %s", resp)
}
td.stop()
}