blob: 813545f482cae7fe5f33aabdfae90bc1e69e6be8 [file] [log] [blame]
Jiri Simsa5293dcb2014-05-10 09:56:38 -07001package signals
2
3import (
4 "fmt"
5 "os"
6 "syscall"
7 "testing"
8
Jiri Simsa519c5072014-09-17 21:37:57 -07009 "veyron.io/veyron/veyron2"
10 "veyron.io/veyron/veyron2/ipc"
11 "veyron.io/veyron/veyron2/mgmt"
12 "veyron.io/veyron/veyron2/naming"
13 "veyron.io/veyron/veyron2/rt"
14 "veyron.io/veyron/veyron2/services/mgmt/appcycle"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070015
Jiri Simsa519c5072014-09-17 21:37:57 -070016 _ "veyron.io/veyron/veyron/lib/testutil"
17 "veyron.io/veyron/veyron/lib/testutil/blackbox"
18 "veyron.io/veyron/veyron/lib/testutil/security"
Cosmos Nicolaoud6c3c9c2014-09-30 15:42:53 -070019 "veyron.io/veyron/veyron/profiles"
Jiri Simsa519c5072014-09-17 21:37:57 -070020 vflag "veyron.io/veyron/veyron/security/flag"
21 "veyron.io/veyron/veyron/services/mgmt/node"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070022)
23
24// TestHelperProcess is boilerplate for the blackbox setup.
25func TestHelperProcess(t *testing.T) {
26 blackbox.HelperProcess(t)
27}
28
29func init() {
30 blackbox.CommandTable["handleDefaults"] = handleDefaults
31 blackbox.CommandTable["handleCustom"] = handleCustom
Bogdan Caprita1002ba42014-06-06 19:24:40 -070032 blackbox.CommandTable["handleCustomWithStop"] = handleCustomWithStop
Jiri Simsa5293dcb2014-05-10 09:56:38 -070033 blackbox.CommandTable["handleDefaultsIgnoreChan"] = handleDefaultsIgnoreChan
34}
35
36func stopLoop(ch chan<- struct{}) {
37 for {
38 switch blackbox.ReadLineFromStdin() {
39 case "close":
40 close(ch)
41 return
42 case "stop":
43 rt.R().Stop()
44 }
45 }
46}
47
48func program(signals ...os.Signal) {
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070049 r := rt.Init()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070050 closeStopLoop := make(chan struct{})
51 go stopLoop(closeStopLoop)
52 wait := ShutdownOnSignals(signals...)
53 fmt.Println("ready")
54 fmt.Println("received signal", <-wait)
Bogdan Caprita4258d882014-07-02 09:15:22 -070055 r.Cleanup()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070056 <-closeStopLoop
57}
58
59func handleDefaults([]string) {
60 program()
61}
62
63func handleCustom([]string) {
64 program(syscall.SIGABRT)
65}
66
Bogdan Caprita1002ba42014-06-06 19:24:40 -070067func handleCustomWithStop([]string) {
68 program(STOP, syscall.SIGABRT, syscall.SIGHUP)
69}
70
Jiri Simsa5293dcb2014-05-10 09:56:38 -070071func handleDefaultsIgnoreChan([]string) {
Bogdan Caprita4258d882014-07-02 09:15:22 -070072 defer rt.Init().Cleanup()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070073 closeStopLoop := make(chan struct{})
74 go stopLoop(closeStopLoop)
75 ShutdownOnSignals()
76 fmt.Println("ready")
77 <-closeStopLoop
78}
79
80func isSignalInSet(sig os.Signal, set []os.Signal) bool {
81 for _, s := range set {
82 if sig == s {
83 return true
84 }
85 }
86 return false
87}
88
89func checkSignalIsDefault(t *testing.T, sig os.Signal) {
90 if !isSignalInSet(sig, defaultSignals()) {
91 t.Errorf("signal %s not in default signal set, as expected", sig)
92 }
93}
94
95func checkSignalIsNotDefault(t *testing.T, sig os.Signal) {
96 if isSignalInSet(sig, defaultSignals()) {
97 t.Errorf("signal %s unexpectedly in default signal set", sig)
98 }
99}
100
101// TestCleanShutdownSignal verifies that sending a signal to a child that
102// handles it by default causes the child to shut down cleanly.
103func TestCleanShutdownSignal(t *testing.T) {
104 c := blackbox.HelperCommand(t, "handleDefaults")
105 defer c.Cleanup()
106 c.Cmd.Start()
107 c.Expect("ready")
108 checkSignalIsDefault(t, syscall.SIGINT)
109 syscall.Kill(c.Cmd.Process.Pid, syscall.SIGINT)
110 c.Expect(fmt.Sprintf("received signal %s", syscall.SIGINT))
111 c.WriteLine("close")
112 c.ExpectEOFAndWait()
113}
114
115// TestCleanShutdownStop verifies that sending a stop comamnd to a child that
116// handles stop commands by default causes the child to shut down cleanly.
117func TestCleanShutdownStop(t *testing.T) {
118 c := blackbox.HelperCommand(t, "handleDefaults")
119 defer c.Cleanup()
120 c.Cmd.Start()
121 c.Expect("ready")
122 c.WriteLine("stop")
Cosmos Nicolaoub07fa772014-05-16 08:07:02 -0700123 c.Expect(fmt.Sprintf("received signal %s", veyron2.LocalStop))
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700124 c.WriteLine("close")
125 c.ExpectEOFAndWait()
126}
127
Bogdan Caprita1002ba42014-06-06 19:24:40 -0700128// TestCleanShutdownStopCustom verifies that sending a stop comamnd to a child
129// that handles stop command as part of a custom set of signals handled, causes
130// the child to shut down cleanly.
131func TestCleanShutdownStopCustom(t *testing.T) {
132 c := blackbox.HelperCommand(t, "handleCustomWithStop")
133 defer c.Cleanup()
134 c.Cmd.Start()
135 c.Expect("ready")
136 c.WriteLine("stop")
137 c.Expect(fmt.Sprintf("received signal %s", veyron2.LocalStop))
138 c.WriteLine("close")
139 c.ExpectEOFAndWait()
140}
141
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700142// TestStopNoHandler verifies that sending a stop command to a child that does
143// not handle stop commands causes the child to exit immediately.
144func TestStopNoHandler(t *testing.T) {
145 c := blackbox.HelperCommand(t, "handleCustom")
146 defer c.Cleanup()
147 c.Cmd.Start()
148 c.Expect("ready")
149 c.WriteLine("stop")
Cosmos Nicolaoub07fa772014-05-16 08:07:02 -0700150 c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", veyron2.UnhandledStopExitCode))
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700151}
152
153// TestDoubleSignal verifies that sending a succession of two signals to a child
154// that handles these signals by default causes the child to exit immediately
155// upon receiving the second signal.
156func TestDoubleSignal(t *testing.T) {
157 c := blackbox.HelperCommand(t, "handleDefaults")
158 defer c.Cleanup()
159 c.Cmd.Start()
160 c.Expect("ready")
161 checkSignalIsDefault(t, syscall.SIGTERM)
162 syscall.Kill(c.Cmd.Process.Pid, syscall.SIGTERM)
163 c.Expect(fmt.Sprintf("received signal %s", syscall.SIGTERM))
164 checkSignalIsDefault(t, syscall.SIGINT)
165 syscall.Kill(c.Cmd.Process.Pid, syscall.SIGINT)
166 c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", DoubleStopExitCode))
167}
168
169// TestSignalAndStop verifies that sending a signal followed by a stop command
170// to a child that handles these by default causes the child to exit immediately
171// upon receiving the stop command.
172func TestSignalAndStop(t *testing.T) {
173 c := blackbox.HelperCommand(t, "handleDefaults")
174 defer c.Cleanup()
175 c.Cmd.Start()
176 c.Expect("ready")
177 checkSignalIsDefault(t, syscall.SIGTERM)
178 syscall.Kill(c.Cmd.Process.Pid, syscall.SIGTERM)
179 c.Expect(fmt.Sprintf("received signal %s", syscall.SIGTERM))
180 c.WriteLine("stop")
181 c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", DoubleStopExitCode))
182}
183
184// TestDoubleStop verifies that sending a succession of stop commands to a child
185// that handles stop commands by default causes the child to exit immediately
186// upon receiving the second stop command.
187func TestDoubleStop(t *testing.T) {
188 c := blackbox.HelperCommand(t, "handleDefaults")
189 defer c.Cleanup()
190 c.Cmd.Start()
191 c.Expect("ready")
192 c.WriteLine("stop")
Cosmos Nicolaoub07fa772014-05-16 08:07:02 -0700193 c.Expect(fmt.Sprintf("received signal %s", veyron2.LocalStop))
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700194 c.WriteLine("stop")
195 c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", DoubleStopExitCode))
196}
197
198// TestSendUnhandledSignal verifies that sending a signal that the child does
199// not handle causes the child to exit as per the signal being sent.
200func TestSendUnhandledSignal(t *testing.T) {
201 c := blackbox.HelperCommand(t, "handleDefaults")
202 defer c.Cleanup()
203 c.Cmd.Start()
204 c.Expect("ready")
205 checkSignalIsNotDefault(t, syscall.SIGABRT)
206 syscall.Kill(c.Cmd.Process.Pid, syscall.SIGABRT)
207 c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status 2"))
208}
209
210// TestDoubleSignalIgnoreChan verifies that, even if we ignore the channel that
211// ShutdownOnSignals returns, sending two signals should still cause the
212// process to exit (ensures that there is no dependency in ShutdownOnSignals
213// on having a goroutine read from the returned channel).
214func TestDoubleSignalIgnoreChan(t *testing.T) {
215 c := blackbox.HelperCommand(t, "handleDefaultsIgnoreChan")
216 defer c.Cleanup()
217 c.Cmd.Start()
218 c.Expect("ready")
219 // Even if we ignore the channel that ShutdownOnSignals returns,
220 // sending two signals should still cause the process to exit.
221 checkSignalIsDefault(t, syscall.SIGTERM)
222 syscall.Kill(c.Cmd.Process.Pid, syscall.SIGTERM)
223 checkSignalIsDefault(t, syscall.SIGINT)
224 syscall.Kill(c.Cmd.Process.Pid, syscall.SIGINT)
225 c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", DoubleStopExitCode))
226}
227
228// TestHandlerCustomSignal verifies that sending a non-default signal to a
229// server that listens for that signal causes the server to shut down cleanly.
230func TestHandlerCustomSignal(t *testing.T) {
231 c := blackbox.HelperCommand(t, "handleCustom")
232 defer c.Cleanup()
233 c.Cmd.Start()
234 c.Expect("ready")
235 checkSignalIsNotDefault(t, syscall.SIGABRT)
236 syscall.Kill(c.Cmd.Process.Pid, syscall.SIGABRT)
237 c.Expect(fmt.Sprintf("received signal %s", syscall.SIGABRT))
238 c.WriteLine("close")
239 c.ExpectEOFAndWait()
240}
Bogdan Caprita1002ba42014-06-06 19:24:40 -0700241
242// TestHandlerCustomSignalWithStop verifies that sending a custom stop signal
243// to a server that listens for that signal causes the server to shut down
244// cleanly, even when a STOP signal is also among the handled signals.
245func TestHandlerCustomSignalWithStop(t *testing.T) {
Jiri Simsa7fc6e872014-06-09 09:22:53 -0700246 for _, signal := range []syscall.Signal{syscall.SIGABRT, syscall.SIGHUP} {
Bogdan Caprita1002ba42014-06-06 19:24:40 -0700247 c := blackbox.HelperCommand(t, "handleCustomWithStop")
248 c.Cmd.Start()
249 c.Expect("ready")
250 checkSignalIsNotDefault(t, signal)
251 syscall.Kill(c.Cmd.Process.Pid, signal)
252 c.Expect(fmt.Sprintf("received signal %s", signal))
253 c.WriteLine("close")
254 c.ExpectEOFAndWait()
255 c.Cleanup()
256 }
257}
258
259// TestParseSignalsList verifies that ShutdownOnSignals correctly interprets
260// the input list of signals.
261func TestParseSignalsList(t *testing.T) {
262 list := []os.Signal{STOP, syscall.SIGTERM}
263 ShutdownOnSignals(list...)
264 if !isSignalInSet(syscall.SIGTERM, list) {
265 t.Errorf("signal %s not in signal set, as expected: %v", syscall.SIGTERM, list)
266 }
267 if !isSignalInSet(STOP, list) {
268 t.Errorf("signal %s not in signal set, as expected: %v", STOP, list)
269 }
270}
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700271
272type configServer struct {
273 ch chan<- string
274}
275
276func (c *configServer) Set(_ ipc.ServerContext, key, value string) error {
277 if key != mgmt.AppCycleManagerConfigKey {
278 return fmt.Errorf("Unexpected key: %v", key)
279 }
280 c.ch <- value
281 return nil
282
283}
284
285func createConfigServer(t *testing.T) (ipc.Server, string, <-chan string) {
286 server, err := rt.R().NewServer()
287 if err != nil {
288 t.Fatalf("Got error: %v", err)
289 }
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700290 ch := make(chan string)
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700291 var ep naming.Endpoint
Cosmos Nicolaoud6c3c9c2014-09-30 15:42:53 -0700292 if ep, err = server.ListenX(profiles.LocalListenSpec); err != nil {
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700293 t.Fatalf("Got error: %v", err)
294 }
Cosmos Nicolaou8bfacf22014-08-19 11:19:36 -0700295 if err := server.Serve("", ipc.LeafDispatcher(node.NewServerConfig(&configServer{ch}), vflag.NewAuthorizerOrDie())); err != nil {
Cosmos Nicolaoufdc838b2014-06-30 21:44:27 -0700296 t.Fatalf("Got error: %v", err)
297 }
298 return server, naming.JoinAddressName(ep.String(), ""), ch
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700299
300}
301
302// TestCleanRemoteShutdown verifies that remote shutdown works correctly.
303func TestCleanRemoteShutdown(t *testing.T) {
304 r := rt.Init()
Bogdan Caprita4258d882014-07-02 09:15:22 -0700305 defer r.Cleanup()
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700306 c := blackbox.HelperCommand(t, "handleDefaults")
307 defer c.Cleanup()
308 // This sets up the child's identity to be derived from the parent's (so
309 // that default authorization works for RPCs between the two).
310 // TODO(caprita): Consider making this boilerplate part of blackbox.
311 id := r.Identity()
Jiri Simsa73e9cac2014-06-30 09:55:10 -0700312 idFile := security.SaveIdentityToFile(security.NewBlessedIdentity(id, "test"))
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700313 defer os.Remove(idFile)
314 configServer, configServiceName, ch := createConfigServer(t)
315 defer configServer.Stop()
316 c.Cmd.Env = append(c.Cmd.Env, fmt.Sprintf("VEYRON_IDENTITY=%v", idFile),
317 fmt.Sprintf("%v=%v", mgmt.ParentNodeManagerConfigKey, configServiceName))
318 c.Cmd.Start()
319 appCycleName := <-ch
320 c.Expect("ready")
321 appCycle, err := appcycle.BindAppCycle(appCycleName)
322 if err != nil {
323 t.Fatalf("Got error: %v", err)
324 }
325 stream, err := appCycle.Stop(r.NewContext())
326 if err != nil {
327 t.Fatalf("Got error: %v", err)
328 }
Shyam Jayaraman97b9dca2014-07-31 13:30:46 -0700329 rStream := stream.RecvStream()
330 if rStream.Advance() || rStream.Err() != nil {
331 t.Errorf("Expected EOF, got (%v, %v) instead: ", rStream.Value(), rStream.Err())
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700332 }
333 if err := stream.Finish(); err != nil {
334 t.Fatalf("Got error: %v", err)
335 }
336 c.Expect(fmt.Sprintf("received signal %s", veyron2.RemoteStop))
337 c.WriteLine("close")
338 c.ExpectEOFAndWait()
339}