blob: 55886109b1a6234b0b429e242ab251bf0422c5ec [file] [log] [blame]
Jiri Simsa5293dcb2014-05-10 09:56:38 -07001package signals
2
3import (
Cosmos Nicolaoucc581722014-10-07 12:45:39 -07004 "bufio"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07005 "fmt"
Cosmos Nicolaoucc581722014-10-07 12:45:39 -07006 "io"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07007 "os"
Cosmos Nicolaoucc581722014-10-07 12:45:39 -07008 "path/filepath"
9 "runtime"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070010 "syscall"
11 "testing"
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070012 "time"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070013
Jiri Simsa519c5072014-09-17 21:37:57 -070014 "veyron.io/veyron/veyron2"
15 "veyron.io/veyron/veyron2/ipc"
16 "veyron.io/veyron/veyron2/mgmt"
17 "veyron.io/veyron/veyron2/naming"
18 "veyron.io/veyron/veyron2/rt"
19 "veyron.io/veyron/veyron2/services/mgmt/appcycle"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070020
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070021 "veyron.io/veyron/veyron/lib/expect"
Asim Shankar95910b62014-10-31 22:02:29 -070022 "veyron.io/veyron/veyron/lib/flags/consts"
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070023 "veyron.io/veyron/veyron/lib/modules"
Asim Shankarc920db32014-10-16 19:18:21 -070024 "veyron.io/veyron/veyron/lib/testutil"
Jiri Simsa519c5072014-09-17 21:37:57 -070025 "veyron.io/veyron/veyron/lib/testutil/security"
Cosmos Nicolaoud6c3c9c2014-09-30 15:42:53 -070026 "veyron.io/veyron/veyron/profiles"
Jiri Simsa519c5072014-09-17 21:37:57 -070027 vflag "veyron.io/veyron/veyron/security/flag"
28 "veyron.io/veyron/veyron/services/mgmt/node"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070029)
30
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070031// TestHelperProcess is boilerplate for the modules setup.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070032func TestHelperProcess(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070033 modules.DispatchInTest()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070034}
35
36func init() {
Asim Shankarc920db32014-10-16 19:18:21 -070037 testutil.Init()
Cosmos Nicolaou1e78ccc2014-10-09 08:10:26 -070038 modules.RegisterChild("handleDefaults", "", handleDefaults)
39 modules.RegisterChild("handleCustom", "", handleCustom)
40 modules.RegisterChild("handleCustomWithStop", "", handleCustomWithStop)
41 modules.RegisterChild("handleDefaultsIgnoreChan", "", handleDefaultsIgnoreChan)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070042}
43
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070044func stopLoop(stdin io.Reader, ch chan<- struct{}) {
45 scanner := bufio.NewScanner(stdin)
46 for scanner.Scan() {
47 switch scanner.Text() {
Jiri Simsa5293dcb2014-05-10 09:56:38 -070048 case "close":
49 close(ch)
50 return
51 case "stop":
52 rt.R().Stop()
53 }
54 }
55}
56
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070057func program(stdin io.Reader, stdout io.Writer, signals ...os.Signal) {
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070058 r := rt.Init()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070059 closeStopLoop := make(chan struct{})
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070060 go stopLoop(stdin, closeStopLoop)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070061 wait := ShutdownOnSignals(signals...)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070062 fmt.Fprintf(stdout, "ready\n")
63 fmt.Fprintf(stdout, "received signal %s\n", <-wait)
Bogdan Caprita4258d882014-07-02 09:15:22 -070064 r.Cleanup()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070065 <-closeStopLoop
66}
67
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070068func handleDefaults(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
69 program(stdin, stdout)
70 return nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -070071}
72
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070073func handleCustom(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
74 program(stdin, stdout, syscall.SIGABRT)
75 return nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -070076}
77
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070078func handleCustomWithStop(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
79 program(stdin, stdout, STOP, syscall.SIGABRT, syscall.SIGHUP)
80 return nil
Bogdan Caprita1002ba42014-06-06 19:24:40 -070081}
82
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070083func handleDefaultsIgnoreChan(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
Bogdan Caprita4258d882014-07-02 09:15:22 -070084 defer rt.Init().Cleanup()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070085 closeStopLoop := make(chan struct{})
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070086 go stopLoop(stdin, closeStopLoop)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070087 ShutdownOnSignals()
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070088 fmt.Fprintf(stdout, "ready\n")
Jiri Simsa5293dcb2014-05-10 09:56:38 -070089 <-closeStopLoop
Cosmos Nicolaoucc581722014-10-07 12:45:39 -070090 return nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -070091}
92
93func isSignalInSet(sig os.Signal, set []os.Signal) bool {
94 for _, s := range set {
95 if sig == s {
96 return true
97 }
98 }
99 return false
100}
101
102func checkSignalIsDefault(t *testing.T, sig os.Signal) {
103 if !isSignalInSet(sig, defaultSignals()) {
104 t.Errorf("signal %s not in default signal set, as expected", sig)
105 }
106}
107
108func checkSignalIsNotDefault(t *testing.T, sig os.Signal) {
109 if isSignalInSet(sig, defaultSignals()) {
110 t.Errorf("signal %s unexpectedly in default signal set", sig)
111 }
112}
113
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700114func newShell(t *testing.T, command string) (*modules.Shell, modules.Handle, *expect.Session) {
115 sh := modules.NewShell()
116 sh.AddSubprocess(command, "")
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700117 handle, err := sh.Start(command, nil)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700118 if err != nil {
119 sh.Cleanup(os.Stderr, os.Stderr)
120 t.Fatalf("unexpected error: %s", err)
121 return nil, nil, nil
122 }
123 session := expect.NewSession(t, handle.Stdout(), time.Minute)
124 return sh, handle, session
125}
126
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700127// TestCleanShutdownSignal verifies that sending a signal to a child that
128// handles it by default causes the child to shut down cleanly.
129func TestCleanShutdownSignal(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700130 sh, h, s := newShell(t, "handleDefaults")
131 defer sh.Cleanup(os.Stderr, os.Stderr)
132 s.Expect("ready")
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700133 checkSignalIsDefault(t, syscall.SIGINT)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700134 syscall.Kill(h.Pid(), syscall.SIGINT)
135 s.Expectf("received signal %s", syscall.SIGINT)
136 fmt.Fprintf(h.Stdin(), "close\n")
137 s.ExpectEOF()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700138}
139
140// TestCleanShutdownStop verifies that sending a stop comamnd to a child that
141// handles stop commands by default causes the child to shut down cleanly.
142func TestCleanShutdownStop(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700143 sh, h, s := newShell(t, "handleDefaults")
144 defer sh.Cleanup(os.Stderr, os.Stderr)
145 s.Expect("ready")
146 fmt.Fprintf(h.Stdin(), "stop\n")
147 s.Expectf("received signal %s", veyron2.LocalStop)
148 fmt.Fprintf(h.Stdin(), "close\n")
149 s.ExpectEOF()
150
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700151}
152
Bogdan Caprita1002ba42014-06-06 19:24:40 -0700153// TestCleanShutdownStopCustom verifies that sending a stop comamnd to a child
154// that handles stop command as part of a custom set of signals handled, causes
155// the child to shut down cleanly.
156func TestCleanShutdownStopCustom(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700157 sh, h, s := newShell(t, "handleCustomWithStop")
158 defer sh.Cleanup(os.Stderr, os.Stderr)
159 s.Expect("ready")
160 fmt.Fprintf(h.Stdin(), "stop\n")
161 s.Expectf("received signal %s", veyron2.LocalStop)
162 fmt.Fprintf(h.Stdin(), "close\n")
163 s.ExpectEOF()
164}
165
166func testExitStatus(t *testing.T, h modules.Handle, s *expect.Session, code int) {
167 s.ExpectEOF()
168 _, file, line, _ := runtime.Caller(1)
169 file = filepath.Base(file)
170 if got, want := h.Shutdown(os.Stdout, os.Stderr), fmt.Errorf("exit status %d", code); got.Error() != want.Error() {
171 t.Errorf("%s:%d: got %q, want %q", file, line, got, want)
172 }
Bogdan Caprita1002ba42014-06-06 19:24:40 -0700173}
174
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700175// TestStopNoHandler verifies that sending a stop command to a child that does
176// not handle stop commands causes the child to exit immediately.
177func TestStopNoHandler(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700178 sh, h, s := newShell(t, "handleCustom")
179 defer sh.Cleanup(os.Stderr, os.Stderr)
180 s.Expect("ready")
181 fmt.Fprintf(h.Stdin(), "stop\n")
182 testExitStatus(t, h, s, veyron2.UnhandledStopExitCode)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700183}
184
185// TestDoubleSignal verifies that sending a succession of two signals to a child
186// that handles these signals by default causes the child to exit immediately
187// upon receiving the second signal.
188func TestDoubleSignal(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700189 sh, h, s := newShell(t, "handleDefaults")
190 defer sh.Cleanup(os.Stderr, os.Stderr)
191 s.Expect("ready")
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700192 checkSignalIsDefault(t, syscall.SIGTERM)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700193 syscall.Kill(h.Pid(), syscall.SIGTERM)
194 s.Expectf("received signal %s", syscall.SIGTERM)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700195 checkSignalIsDefault(t, syscall.SIGINT)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700196 syscall.Kill(h.Pid(), syscall.SIGINT)
197 testExitStatus(t, h, s, DoubleStopExitCode)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700198}
199
200// TestSignalAndStop verifies that sending a signal followed by a stop command
201// to a child that handles these by default causes the child to exit immediately
202// upon receiving the stop command.
203func TestSignalAndStop(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700204 sh, h, s := newShell(t, "handleDefaults")
205 defer sh.Cleanup(os.Stderr, os.Stderr)
206 s.Expect("ready")
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700207 checkSignalIsDefault(t, syscall.SIGTERM)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700208 syscall.Kill(h.Pid(), syscall.SIGTERM)
209 s.Expectf("received signal %s", syscall.SIGTERM)
210 fmt.Fprintf(h.Stdin(), "stop\n")
211 testExitStatus(t, h, s, DoubleStopExitCode)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700212}
213
214// TestDoubleStop verifies that sending a succession of stop commands to a child
215// that handles stop commands by default causes the child to exit immediately
216// upon receiving the second stop command.
217func TestDoubleStop(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700218 sh, h, s := newShell(t, "handleDefaults")
219 defer sh.Cleanup(os.Stderr, os.Stderr)
220 s.Expect("ready")
221 fmt.Fprintf(h.Stdin(), "stop\n")
222 s.Expectf("received signal %s", veyron2.LocalStop)
223 fmt.Fprintf(h.Stdin(), "stop\n")
224 testExitStatus(t, h, s, DoubleStopExitCode)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700225}
226
227// TestSendUnhandledSignal verifies that sending a signal that the child does
228// not handle causes the child to exit as per the signal being sent.
229func TestSendUnhandledSignal(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700230 sh, h, s := newShell(t, "handleDefaults")
231 defer sh.Cleanup(os.Stderr, os.Stderr)
232 s.Expect("ready")
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700233 checkSignalIsNotDefault(t, syscall.SIGABRT)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700234 syscall.Kill(h.Pid(), syscall.SIGABRT)
235 testExitStatus(t, h, s, 2)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700236}
237
238// TestDoubleSignalIgnoreChan verifies that, even if we ignore the channel that
239// ShutdownOnSignals returns, sending two signals should still cause the
240// process to exit (ensures that there is no dependency in ShutdownOnSignals
241// on having a goroutine read from the returned channel).
242func TestDoubleSignalIgnoreChan(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700243 sh, h, s := newShell(t, "handleDefaultsIgnoreChan")
244 defer sh.Cleanup(os.Stderr, os.Stderr)
245 s.Expect("ready")
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700246 // Even if we ignore the channel that ShutdownOnSignals returns,
247 // sending two signals should still cause the process to exit.
248 checkSignalIsDefault(t, syscall.SIGTERM)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700249 syscall.Kill(h.Pid(), syscall.SIGTERM)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700250 checkSignalIsDefault(t, syscall.SIGINT)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700251 syscall.Kill(h.Pid(), syscall.SIGINT)
252 testExitStatus(t, h, s, DoubleStopExitCode)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700253}
254
255// TestHandlerCustomSignal verifies that sending a non-default signal to a
256// server that listens for that signal causes the server to shut down cleanly.
257func TestHandlerCustomSignal(t *testing.T) {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700258 sh, h, s := newShell(t, "handleCustom")
259 defer sh.Cleanup(os.Stderr, os.Stderr)
260 s.Expect("ready")
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700261 checkSignalIsNotDefault(t, syscall.SIGABRT)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700262 syscall.Kill(h.Pid(), syscall.SIGABRT)
263 s.Expectf("received signal %s", syscall.SIGABRT)
264 fmt.Fprintf(h.Stdin(), "stop\n")
265 s.ExpectEOF()
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700266}
Bogdan Caprita1002ba42014-06-06 19:24:40 -0700267
268// TestHandlerCustomSignalWithStop verifies that sending a custom stop signal
269// to a server that listens for that signal causes the server to shut down
270// cleanly, even when a STOP signal is also among the handled signals.
271func TestHandlerCustomSignalWithStop(t *testing.T) {
Jiri Simsa7fc6e872014-06-09 09:22:53 -0700272 for _, signal := range []syscall.Signal{syscall.SIGABRT, syscall.SIGHUP} {
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700273 sh, h, s := newShell(t, "handleCustomWithStop")
274 s.Expect("ready")
Bogdan Caprita1002ba42014-06-06 19:24:40 -0700275 checkSignalIsNotDefault(t, signal)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700276 syscall.Kill(h.Pid(), signal)
277 s.Expectf("received signal %s", signal)
278 fmt.Fprintf(h.Stdin(), "close\n")
279 s.ExpectEOF()
280 sh.Cleanup(os.Stderr, os.Stderr)
Bogdan Caprita1002ba42014-06-06 19:24:40 -0700281 }
282}
283
284// TestParseSignalsList verifies that ShutdownOnSignals correctly interprets
285// the input list of signals.
286func TestParseSignalsList(t *testing.T) {
287 list := []os.Signal{STOP, syscall.SIGTERM}
288 ShutdownOnSignals(list...)
289 if !isSignalInSet(syscall.SIGTERM, list) {
290 t.Errorf("signal %s not in signal set, as expected: %v", syscall.SIGTERM, list)
291 }
292 if !isSignalInSet(STOP, list) {
293 t.Errorf("signal %s not in signal set, as expected: %v", STOP, list)
294 }
295}
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700296
297type configServer struct {
298 ch chan<- string
299}
300
301func (c *configServer) Set(_ ipc.ServerContext, key, value string) error {
302 if key != mgmt.AppCycleManagerConfigKey {
303 return fmt.Errorf("Unexpected key: %v", key)
304 }
305 c.ch <- value
306 return nil
307
308}
309
310func createConfigServer(t *testing.T) (ipc.Server, string, <-chan string) {
311 server, err := rt.R().NewServer()
312 if err != nil {
313 t.Fatalf("Got error: %v", err)
314 }
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700315 ch := make(chan string)
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700316 var ep naming.Endpoint
Cosmos Nicolaouf8d4c2b2014-10-23 22:36:38 -0700317 if ep, err = server.Listen(profiles.LocalListenSpec); err != nil {
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700318 t.Fatalf("Got error: %v", err)
319 }
Todd Wang702385a2014-11-07 01:54:08 -0800320 if err := server.Serve("", node.ConfigServer(&configServer{ch}), vflag.NewAuthorizerOrDie()); err != nil {
Cosmos Nicolaoufdc838b2014-06-30 21:44:27 -0700321 t.Fatalf("Got error: %v", err)
322 }
323 return server, naming.JoinAddressName(ep.String(), ""), ch
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700324
325}
326
327// TestCleanRemoteShutdown verifies that remote shutdown works correctly.
328func TestCleanRemoteShutdown(t *testing.T) {
Asim Shankar7b5c94a2014-10-28 21:42:56 -0700329 r := rt.Init()
Bogdan Caprita4258d882014-07-02 09:15:22 -0700330 defer r.Cleanup()
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700331
332 sh := modules.NewShell()
333 sh.AddSubprocess("handleDefaults", "")
334 defer sh.Cleanup(os.Stderr, os.Stderr)
335
Asim Shankar88292912014-10-09 19:41:07 -0700336 // Set the child process up with a blessing from the parent so that
337 // the default authorization works for RPCs between the two.
338 childcreds := security.NewVeyronCredentials(r.Principal(), "child")
339 defer os.RemoveAll(childcreds)
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700340 configServer, configServiceName, ch := createConfigServer(t)
341 defer configServer.Stop()
Asim Shankar95910b62014-10-31 22:02:29 -0700342 sh.SetVar(consts.VeyronCredentials, childcreds)
Jiri Simsaf57930f2014-11-05 15:19:31 -0800343 sh.Config.Set(mgmt.ParentNameConfigKey, configServiceName)
344 sh.Config.Set(mgmt.ProtocolConfigKey, "tcp")
345 sh.Config.Set(mgmt.AddressConfigKey, "127.0.0.1:0")
Cosmos Nicolaou612ad382014-10-29 19:41:35 -0700346 h, err := sh.Start("handleDefaults", nil)
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700347 if err != nil {
348 t.Fatalf("unexpected error: %s", err)
349 }
350 s := expect.NewSession(t, h.Stdout(), time.Minute)
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700351 appCycleName := <-ch
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700352 s.Expect("ready")
Todd Wang702385a2014-11-07 01:54:08 -0800353 appCycle := appcycle.AppCycleClient(appCycleName)
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700354 stream, err := appCycle.Stop(r.NewContext())
355 if err != nil {
356 t.Fatalf("Got error: %v", err)
357 }
Shyam Jayaraman97b9dca2014-07-31 13:30:46 -0700358 rStream := stream.RecvStream()
359 if rStream.Advance() || rStream.Err() != nil {
360 t.Errorf("Expected EOF, got (%v, %v) instead: ", rStream.Value(), rStream.Err())
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700361 }
362 if err := stream.Finish(); err != nil {
363 t.Fatalf("Got error: %v", err)
364 }
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700365 s.Expectf("received signal %s", veyron2.RemoteStop)
366 fmt.Fprintf(h.Stdin(), "close\n")
367 s.ExpectEOF()
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700368}